Merge branch 'master' into f-validate-list-no-duplicates
This commit is contained in:
commit
f17e14f576
|
@ -2,8 +2,7 @@ dist: trusty
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.8.3
|
||||
- 1.9rc1
|
||||
- 1.9
|
||||
|
||||
# add TF_CONSUL_TEST=1 to run consul tests
|
||||
# they were causing timouts in travis
|
||||
|
|
|
@ -22,6 +22,11 @@ IMPROVEMENTS:
|
|||
* backend/consul: can now set the path to a specific CA certificate file, client certificate file, and client key file that will be used when configuring the underlying Consul client. [GH-15405]
|
||||
* backend/http: now has optional support for locking, with special support from the target server. Additionally, the update operation can now optionally be implemented via `PUT` rather than `POST`. [GH-15793]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* cli: `terraform init` now verifies the required Terraform version from the root module config. Previously this was verified only on subsequent commands, after initialization. [GH-15935]
|
||||
* cli: `terraform validate` now consults `terraform.tfvars`, if present, to set variable values. This is now consistent with the behavior of other commands. [GH-15938]
|
||||
|
||||
## 0.10.2 (August 16, 2017)
|
||||
|
||||
BUG FIXES:
|
||||
|
|
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)
|
||||
|
||||
default: test vet
|
||||
|
@ -27,10 +27,11 @@ plugin-dev: generate
|
|||
mv $(GOPATH)/bin/$(PLUGIN) $(GOPATH)/bin/terraform-$(PLUGIN)
|
||||
|
||||
# test runs the unit tests
|
||||
# we run this one package at a time here because running the entire suite in
|
||||
# one command creates memory usage issues when running in Travis-CI.
|
||||
test: fmtcheck generate
|
||||
go test -i $(TEST) || exit 1
|
||||
echo $(TEST) | \
|
||||
xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4
|
||||
go list $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4
|
||||
|
||||
# testacc runs acceptance tests
|
||||
testacc: fmtcheck generate
|
||||
|
@ -64,8 +65,8 @@ cover:
|
|||
# vet runs the Go source code static analysis tool `vet` to find
|
||||
# any common errors.
|
||||
vet:
|
||||
@echo 'go vet $$(go list ./... | grep -v /terraform/vendor/)'
|
||||
@go vet $$(go list ./... | grep -v /terraform/vendor/) ; if [ $$? -eq 1 ]; then \
|
||||
@echo 'go vet ./...'
|
||||
@go vet ./... ; if [ $$? -eq 1 ]; then \
|
||||
echo ""; \
|
||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||
echo "and fix them if necessary before submitting the code for review."; \
|
||||
|
@ -78,7 +79,7 @@ generate:
|
|||
@which stringer > /dev/null; if [ $$? -ne 0 ]; then \
|
||||
go get -u golang.org/x/tools/cmd/stringer; \
|
||||
fi
|
||||
go generate $$(go list ./... | grep -v /terraform/vendor/)
|
||||
go generate ./...
|
||||
@go fmt command/internal_plugin_list.go > /dev/null
|
||||
|
||||
fmt:
|
||||
|
|
|
@ -35,7 +35,7 @@ All documentation is available on the [Terraform website](http://www.terraform.i
|
|||
Developing Terraform
|
||||
--------------------
|
||||
|
||||
If you wish to work on Terraform itself or any of its built-in providers, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.8+ is *required*). Alternatively, you can use the Vagrantfile in the root of this repo to stand up a virtual machine with the appropriate dev tooling already set up for you.
|
||||
If you wish to work on Terraform itself or any of its built-in providers, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.9+ is *required*). Alternatively, you can use the Vagrantfile in the root of this repo to stand up a virtual machine with the appropriate dev tooling already set up for you.
|
||||
|
||||
This repository contains only Terraform core, which includes the command line interface and the main graph engine. Providers are implemented as plugins that each have their own repository in [the `terraform-providers` organization](https://github.com/terraform-providers) on GitHub. Instructions for developing each provider are in the associated README file. For more information, see [the provider development overview](https://www.terraform.io/docs/plugins/provider.html).
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
# Software version variables
|
||||
GOVERSION = "1.8.3"
|
||||
GOVERSION = "1.9"
|
||||
UBUNTUVERSION = "16.04"
|
||||
|
||||
# CPU and RAM can be adjusted depending on your system
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
// TestBackendConfig validates and configures the backend with the
|
||||
// given configuration.
|
||||
func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backend {
|
||||
t.Helper()
|
||||
|
||||
// Get the proper config structure
|
||||
rc, err := config.NewRawConfig(c)
|
||||
if err != nil {
|
||||
|
@ -45,6 +47,8 @@ func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backen
|
|||
// If you want to test locking, two backends must be given. If b2 is nil,
|
||||
// then state lockign won't be tested.
|
||||
func TestBackend(t *testing.T, b1, b2 Backend) {
|
||||
t.Helper()
|
||||
|
||||
testBackendStates(t, b1)
|
||||
|
||||
if b2 != nil {
|
||||
|
@ -53,6 +57,8 @@ func TestBackend(t *testing.T, b1, b2 Backend) {
|
|||
}
|
||||
|
||||
func testBackendStates(t *testing.T, b Backend) {
|
||||
t.Helper()
|
||||
|
||||
states, err := b.States()
|
||||
if err == ErrNamedStatesNotSupported {
|
||||
t.Logf("TestBackend: named states not supported in %T, skipping", b)
|
||||
|
@ -231,6 +237,8 @@ func testBackendStates(t *testing.T, b Backend) {
|
|||
}
|
||||
|
||||
func testBackendStateLock(t *testing.T, b1, b2 Backend) {
|
||||
t.Helper()
|
||||
|
||||
// Get the default state for each
|
||||
b1StateMgr, err := b1.State(DefaultStateName)
|
||||
if err != nil {
|
||||
|
|
|
@ -54,6 +54,8 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func tempDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
dir, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -99,6 +101,8 @@ func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr ter
|
|||
}
|
||||
|
||||
func testModule(t *testing.T, name string) *module.Tree {
|
||||
t.Helper()
|
||||
|
||||
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -114,6 +118,8 @@ func testModule(t *testing.T, name string) *module.Tree {
|
|||
|
||||
// testPlan returns a non-nil noop plan.
|
||||
func testPlan(t *testing.T) *terraform.Plan {
|
||||
t.Helper()
|
||||
|
||||
state := terraform.NewState()
|
||||
state.RootModule().Outputs["foo"] = &terraform.OutputState{
|
||||
Type: "string",
|
||||
|
@ -127,6 +133,8 @@ func testPlan(t *testing.T) *terraform.Plan {
|
|||
}
|
||||
|
||||
func testPlanFile(t *testing.T, plan *terraform.Plan) string {
|
||||
t.Helper()
|
||||
|
||||
path := testTempFile(t)
|
||||
|
||||
f, err := os.Create(path)
|
||||
|
@ -143,6 +151,8 @@ func testPlanFile(t *testing.T, plan *terraform.Plan) string {
|
|||
}
|
||||
|
||||
func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
||||
t.Helper()
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -180,6 +190,8 @@ func testState() *terraform.State {
|
|||
}
|
||||
|
||||
func testStateFile(t *testing.T, s *terraform.State) string {
|
||||
t.Helper()
|
||||
|
||||
path := testTempFile(t)
|
||||
|
||||
f, err := os.Create(path)
|
||||
|
@ -198,6 +210,8 @@ func testStateFile(t *testing.T, s *terraform.State) string {
|
|||
// testStateFileDefault writes the state out to the default statefile
|
||||
// in the cwd. Use `testCwd` to change into a temp cwd.
|
||||
func testStateFileDefault(t *testing.T, s *terraform.State) string {
|
||||
t.Helper()
|
||||
|
||||
f, err := os.Create(DefaultStateFilename)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -214,6 +228,8 @@ func testStateFileDefault(t *testing.T, s *terraform.State) string {
|
|||
// testStateFileRemote writes the state out to the remote statefile
|
||||
// in the cwd. Use `testCwd` to change into a temp cwd.
|
||||
func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -234,6 +250,8 @@ func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
|||
|
||||
// testStateRead reads the state from a file
|
||||
func testStateRead(t *testing.T, path string) *terraform.State {
|
||||
t.Helper()
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -251,6 +269,8 @@ func testStateRead(t *testing.T, path string) *terraform.State {
|
|||
// testStateOutput tests that the state at the given path contains
|
||||
// the expected state string.
|
||||
func testStateOutput(t *testing.T, path string, expected string) {
|
||||
t.Helper()
|
||||
|
||||
newState := testStateRead(t, path)
|
||||
actual := strings.TrimSpace(newState.String())
|
||||
expected = strings.TrimSpace(expected)
|
||||
|
@ -277,10 +297,14 @@ func testProvider() *terraform.MockResourceProvider {
|
|||
}
|
||||
|
||||
func testTempFile(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
return filepath.Join(testTempDir(t), "state.tfstate")
|
||||
}
|
||||
|
||||
func testTempDir(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
d, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -292,6 +316,8 @@ func testTempDir(t *testing.T) string {
|
|||
// testRename renames the path to new and returns a function to defer to
|
||||
// revert the rename.
|
||||
func testRename(t *testing.T, base, path, new string) func() {
|
||||
t.Helper()
|
||||
|
||||
if base != "" {
|
||||
path = filepath.Join(base, path)
|
||||
new = filepath.Join(base, new)
|
||||
|
@ -310,6 +336,8 @@ func testRename(t *testing.T, base, path, new string) func() {
|
|||
// testChdir changes the directory and returns a function to defer to
|
||||
// revert the old cwd.
|
||||
func testChdir(t *testing.T, new string) func() {
|
||||
t.Helper()
|
||||
|
||||
old, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -328,6 +356,8 @@ func testChdir(t *testing.T, new string) func() {
|
|||
// testCwd is used to change the current working directory
|
||||
// into a test directory that should be remoted after
|
||||
func testCwd(t *testing.T) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
tmp, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
|
@ -347,6 +377,8 @@ func testCwd(t *testing.T) (string, string) {
|
|||
|
||||
// testFixCwd is used to as a defer to testDir
|
||||
func testFixCwd(t *testing.T, tmp, cwd string) {
|
||||
t.Helper()
|
||||
|
||||
if err := os.Chdir(cwd); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -362,6 +394,8 @@ func testFixCwd(t *testing.T, tmp, cwd string) {
|
|||
// The returned function should be deferred to properly clean up and restore
|
||||
// the original stdin.
|
||||
func testStdinPipe(t *testing.T, src io.Reader) func() {
|
||||
t.Helper()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -390,6 +424,8 @@ func testStdinPipe(t *testing.T, src io.Reader) func() {
|
|||
// not useful since the commands are configured to write to a cli.Ui, not
|
||||
// Stdout directly. Commands like `console` though use the raw stdout.
|
||||
func testStdoutCapture(t *testing.T, dst io.Writer) func() {
|
||||
t.Helper()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -424,6 +460,8 @@ func testStdoutCapture(t *testing.T, dst io.Writer) func() {
|
|||
// in order to interactive prompts. The returned function must be called
|
||||
// in a defer to clean up.
|
||||
func testInteractiveInput(t *testing.T, answers []string) func() {
|
||||
t.Helper()
|
||||
|
||||
// Disable test mode so input is called
|
||||
test = false
|
||||
|
||||
|
@ -443,6 +481,8 @@ func testInteractiveInput(t *testing.T, answers []string) func() {
|
|||
// for calls to Input when the right question is asked. The key is the
|
||||
// question "Id" that is used.
|
||||
func testInputMap(t *testing.T, answers map[string]string) func() {
|
||||
t.Helper()
|
||||
|
||||
// Disable test mode so input is called
|
||||
test = false
|
||||
|
||||
|
@ -465,6 +505,8 @@ func testInputMap(t *testing.T, answers map[string]string) func() {
|
|||
// backend. This returns the complete state that can be saved. Use
|
||||
// `testStateFileRemote` to write the returned state.
|
||||
func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
var b64md5 string
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
|
@ -507,6 +549,8 @@ func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State
|
|||
// testRemoteState is used to make a test HTTP server to return a given
|
||||
// state file that can be used for testing legacy remote state.
|
||||
func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
var b64md5 string
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ func canAccessNetwork() bool {
|
|||
}
|
||||
|
||||
func skipIfCannotAccessNetwork(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
if !canAccessNetwork() {
|
||||
t.Skip("network access not allowed; use TF_ACC=1 to enable")
|
||||
}
|
||||
|
|
|
@ -288,6 +288,11 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade
|
|||
return err
|
||||
}
|
||||
|
||||
if err := terraform.CheckRequiredVersion(mod); err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
var available discovery.PluginMetaSet
|
||||
if upgrade {
|
||||
// If we're in upgrade mode, we ignore any auto-installed plugins
|
||||
|
|
|
@ -855,6 +855,27 @@ func TestInit_getProviderHaveLegacyVersion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInit_getProviderCheckRequiredVersion(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("init-check-required-version"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 1 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_providerLockFile(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
|
|
|
@ -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 = "."
|
||||
|
||||
func (c *ValidateCommand) Run(args []string) int {
|
||||
args, err := c.Meta.process(args, false)
|
||||
args, err := c.Meta.process(args, true)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/copy"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
|
@ -28,6 +30,28 @@ func TestValidateCommand(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateCommandWithTfvarsFile(t *testing.T) {
|
||||
// Create a temporary working directory that is empty because this test
|
||||
// requires scanning the current working directory by validate command.
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("validate-valid/with-tfvars-file"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &ValidateCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFailingCommand(t *testing.T) {
|
||||
if ui, code := setupTest("validate-invalid"); code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
|
|
|
@ -143,6 +143,10 @@ func outputsStr(os []*Output) string {
|
|||
result += fmt.Sprintf(" %s: %s\n", kind, str)
|
||||
}
|
||||
}
|
||||
|
||||
if o.Description != "" {
|
||||
result += fmt.Sprintf(" description\n %s\n", o.Description)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(result)
|
||||
|
|
|
@ -499,6 +499,7 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
|
|||
|
||||
// Delete special keys
|
||||
delete(config, "depends_on")
|
||||
delete(config, "description")
|
||||
|
||||
rawConfig, err := NewRawConfig(config)
|
||||
if err != nil {
|
||||
|
@ -520,10 +521,23 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// If we have a description field, then filter that
|
||||
var description string
|
||||
if o := listVal.Filter("description"); len(o.Items) > 0 {
|
||||
err := hcl.DecodeObject(&description, o.Items[0].Val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Error reading description for output %q: %s",
|
||||
n,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, &Output{
|
||||
Name: n,
|
||||
RawConfig: rawConfig,
|
||||
DependsOn: dependsOn,
|
||||
Name: n,
|
||||
RawConfig: rawConfig,
|
||||
DependsOn: dependsOn,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1058,6 +1058,11 @@ aws_instance.test (x1)
|
|||
`
|
||||
|
||||
const basicOutputsStr = `
|
||||
web_id
|
||||
vars
|
||||
resource: aws_instance.web.id
|
||||
description
|
||||
The ID
|
||||
web_ip
|
||||
vars
|
||||
resource: aws_instance.web.private_ip
|
||||
|
|
|
@ -85,6 +85,11 @@ output "web_ip" {
|
|||
value = "${aws_instance.web.private_ip}"
|
||||
}
|
||||
|
||||
output "web_id" {
|
||||
description = "The ID"
|
||||
value = "${aws_instance.web.id}"
|
||||
}
|
||||
|
||||
atlas {
|
||||
name = "mitchellh/foo"
|
||||
}
|
||||
|
|
|
@ -88,6 +88,10 @@
|
|||
},
|
||||
|
||||
"output": {
|
||||
"web_id": {
|
||||
"description": "The ID",
|
||||
"value": "${aws_instance.web.id}"
|
||||
},
|
||||
"web_ip": {
|
||||
"value": "${aws_instance.web.private_ip}"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
|
||||
// TestRawConfig is used to create a RawConfig for testing.
|
||||
func TestRawConfig(t *testing.T, c map[string]interface{}) *RawConfig {
|
||||
t.Helper()
|
||||
|
||||
cfg, err := NewRawConfig(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
// TestResourceDataRaw creates a ResourceData from a raw configuration map.
|
||||
func TestResourceDataRaw(
|
||||
t *testing.T, schema map[string]*Schema, raw map[string]interface{}) *ResourceData {
|
||||
t.Helper()
|
||||
|
||||
c, err := config.NewRawConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
|
|
@ -3,6 +3,7 @@ package validation
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
@ -138,6 +139,8 @@ func CIDRNetwork(min, max int) schema.SchemaValidateFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// ValidateJsonString is a SchemaValidateFunc which tests to make sure the
|
||||
// supplied string is valid JSON.
|
||||
func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) {
|
||||
if _, err := structure.NormalizeJsonString(v); err != nil {
|
||||
errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err))
|
||||
|
@ -156,5 +159,13 @@ func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []e
|
|||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ValidateRegexp returns a SchemaValidateFunc which tests to make sure the
|
||||
// supplied string is a valid regular expression.
|
||||
func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) {
|
||||
if _, err := regexp.Compile(v.(string)); err != nil {
|
||||
errors = append(errors, fmt.Errorf("%q: %s", k, err))
|
||||
}
|
||||
return
|
||||
}
|
|
@ -111,6 +111,20 @@ func TestValidationStringInSlice(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestValidationRegexp(t *testing.T) {
|
||||
runTestCases(t, []testCase{
|
||||
{
|
||||
val: ".*foo.*",
|
||||
f: ValidateRegexp,
|
||||
},
|
||||
{
|
||||
val: "foo(bar",
|
||||
f: ValidateRegexp,
|
||||
expectedErr: regexp.MustCompile(regexp.QuoteMeta("error parsing regexp: missing closing ): `foo(bar`")),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateJsonString(t *testing.T) {
|
||||
type testCases struct {
|
||||
Value string
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
// that the given implementation is pre-loaded with the TestStateInitial
|
||||
// state.
|
||||
func TestState(t *testing.T, s State) {
|
||||
t.Helper()
|
||||
|
||||
if err := s.RefreshState(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/helper/experiment"
|
||||
)
|
||||
|
||||
// InputMode defines what sort of input will be asked for when Input
|
||||
|
@ -123,7 +122,7 @@ type Context struct {
|
|||
func NewContext(opts *ContextOpts) (*Context, error) {
|
||||
// Validate the version requirement if it is given
|
||||
if opts.Module != nil {
|
||||
if err := checkRequiredVersion(opts.Module); err != nil {
|
||||
if err := CheckRequiredVersion(opts.Module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -465,7 +464,7 @@ func (c *Context) Input(mode InputMode) error {
|
|||
}
|
||||
|
||||
// Do the walk
|
||||
if _, err := c.walk(graph, nil, walkInput); err != nil {
|
||||
if _, err := c.walk(graph, walkInput); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -506,7 +505,7 @@ func (c *Context) Apply() (*State, error) {
|
|||
}
|
||||
|
||||
// Walk the graph
|
||||
walker, err := c.walk(graph, graph, operation)
|
||||
walker, err := c.walk(graph, operation)
|
||||
if len(walker.ValidationErrors) > 0 {
|
||||
err = multierror.Append(err, walker.ValidationErrors...)
|
||||
}
|
||||
|
@ -575,7 +574,7 @@ func (c *Context) Plan() (*Plan, error) {
|
|||
}
|
||||
|
||||
// Do the walk
|
||||
walker, err := c.walk(graph, graph, operation)
|
||||
walker, err := c.walk(graph, operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -630,7 +629,7 @@ func (c *Context) Refresh() (*State, error) {
|
|||
}
|
||||
|
||||
// Do the walk
|
||||
if _, err := c.walk(graph, graph, walkRefresh); err != nil {
|
||||
if _, err := c.walk(graph, walkRefresh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -705,7 +704,7 @@ func (c *Context) Validate() ([]string, []error) {
|
|||
}
|
||||
|
||||
// Walk
|
||||
walker, err := c.walk(graph, graph, walkValidate)
|
||||
walker, err := c.walk(graph, walkValidate)
|
||||
if err != nil {
|
||||
return nil, multierror.Append(errs, err).Errors
|
||||
}
|
||||
|
@ -792,33 +791,11 @@ func (c *Context) releaseRun() {
|
|||
c.runContext = nil
|
||||
}
|
||||
|
||||
func (c *Context) walk(
|
||||
graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) {
|
||||
func (c *Context) walk(graph *Graph, operation walkOperation) (*ContextGraphWalker, error) {
|
||||
// Keep track of the "real" context which is the context that does
|
||||
// the real work: talking to real providers, modifying real state, etc.
|
||||
realCtx := c
|
||||
|
||||
// If we don't want shadowing, remove it
|
||||
if !experiment.Enabled(experiment.X_shadow) {
|
||||
shadow = nil
|
||||
}
|
||||
|
||||
// Just log this so we can see it in a debug log
|
||||
if !c.shadow {
|
||||
log.Printf("[WARN] terraform: shadow graph disabled")
|
||||
shadow = nil
|
||||
}
|
||||
|
||||
// If we have a shadow graph, walk that as well
|
||||
var shadowCtx *Context
|
||||
var shadowCloser Shadow
|
||||
if shadow != nil {
|
||||
// Build the shadow context. In the process, override the real context
|
||||
// with the one that is wrapped so that the shadow context can verify
|
||||
// the results of the real.
|
||||
realCtx, shadowCtx, shadowCloser = newShadowContext(c)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
|
||||
|
||||
walker := &ContextGraphWalker{
|
||||
|
@ -837,90 +814,6 @@ func (c *Context) walk(
|
|||
close(watchStop)
|
||||
<-watchWait
|
||||
|
||||
// If we have a shadow graph and we interrupted the real graph, then
|
||||
// we just close the shadow and never verify it. It is non-trivial to
|
||||
// recreate the exact execution state up until an interruption so this
|
||||
// isn't supported with shadows at the moment.
|
||||
if shadowCloser != nil && c.sh.Stopped() {
|
||||
// Ignore the error result, there is nothing we could care about
|
||||
shadowCloser.CloseShadow()
|
||||
|
||||
// Set it to nil so we don't do anything
|
||||
shadowCloser = nil
|
||||
}
|
||||
|
||||
// If we have a shadow graph, wait for that to complete.
|
||||
if shadowCloser != nil {
|
||||
// Build the graph walker for the shadow. We also wrap this in
|
||||
// a panicwrap so that panics are captured. For the shadow graph,
|
||||
// we just want panics to be normal errors rather than to crash
|
||||
// Terraform.
|
||||
shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
|
||||
Context: shadowCtx,
|
||||
Operation: operation,
|
||||
})
|
||||
|
||||
// Kick off the shadow walk. This will block on any operations
|
||||
// on the real walk so it is fine to start first.
|
||||
log.Printf("[INFO] Starting shadow graph walk: %s", operation.String())
|
||||
shadowCh := make(chan error)
|
||||
go func() {
|
||||
shadowCh <- shadow.Walk(shadowWalker)
|
||||
}()
|
||||
|
||||
// Notify the shadow that we're done
|
||||
if err := shadowCloser.CloseShadow(); err != nil {
|
||||
c.shadowErr = multierror.Append(c.shadowErr, err)
|
||||
}
|
||||
|
||||
// Wait for the walk to end
|
||||
log.Printf("[DEBUG] Waiting for shadow graph to complete...")
|
||||
shadowWalkErr := <-shadowCh
|
||||
|
||||
// Get any shadow errors
|
||||
if err := shadowCloser.ShadowError(); err != nil {
|
||||
c.shadowErr = multierror.Append(c.shadowErr, err)
|
||||
}
|
||||
|
||||
// Verify the contexts (compare)
|
||||
if err := shadowContextVerify(realCtx, shadowCtx); err != nil {
|
||||
c.shadowErr = multierror.Append(c.shadowErr, err)
|
||||
}
|
||||
|
||||
// At this point, if we're supposed to fail on error, then
|
||||
// we PANIC. Some tests just verify that there is an error,
|
||||
// so simply appending it to realErr and returning could hide
|
||||
// shadow problems.
|
||||
//
|
||||
// This must be done BEFORE appending shadowWalkErr since the
|
||||
// shadowWalkErr may include expected errors.
|
||||
//
|
||||
// We only do this if we don't have a real error. In the case of
|
||||
// a real error, we can't guarantee what nodes were and weren't
|
||||
// traversed in parallel scenarios so we can't guarantee no
|
||||
// shadow errors.
|
||||
if c.shadowErr != nil && contextFailOnShadowError && realErr == nil {
|
||||
panic(multierror.Prefix(c.shadowErr, "shadow graph:"))
|
||||
}
|
||||
|
||||
// Now, if we have a walk error, we append that through
|
||||
if shadowWalkErr != nil {
|
||||
c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr)
|
||||
}
|
||||
|
||||
if c.shadowErr == nil {
|
||||
log.Printf("[INFO] Shadow graph success!")
|
||||
} else {
|
||||
log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr)
|
||||
|
||||
// If we're supposed to fail on shadow errors, then report it
|
||||
if contextFailOnShadowError {
|
||||
realErr = multierror.Append(realErr, multierror.Prefix(
|
||||
c.shadowErr, "shadow graph:"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return walker, realErr
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ func (c *Context) Import(opts *ImportOpts) (*State, error) {
|
|||
}
|
||||
|
||||
// Walk it
|
||||
if _, err := c.walk(graph, nil, walkImport); err != nil {
|
||||
if _, err := c.walk(graph, walkImport); err != nil {
|
||||
return c.state, err
|
||||
}
|
||||
|
||||
|
|
|
@ -1005,7 +1005,7 @@ func TestContext2Validate_PlanGraphBuilder(t *testing.T) {
|
|||
t.Fatalf("error attmepting to Build PlanGraphBuilder: %s", err)
|
||||
}
|
||||
defer c.acquireRun("validate-test")()
|
||||
walker, err := c.walk(graph, graph, walkValidate)
|
||||
walker, err := c.walk(graph, walkValidate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
t.Helper()
|
||||
|
||||
dir, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -63,6 +65,8 @@ func tempDir(t *testing.T) string {
|
|||
// a function to defer to reset the old value.
|
||||
// the old value that should be set via a defer.
|
||||
func tempEnv(t *testing.T, k string, v string) func() {
|
||||
t.Helper()
|
||||
|
||||
old, oldOk := os.LookupEnv(k)
|
||||
os.Setenv(k, v)
|
||||
return func() {
|
||||
|
@ -75,6 +79,8 @@ func tempEnv(t *testing.T, k string, v string) func() {
|
|||
}
|
||||
|
||||
func testConfig(t *testing.T, name string) *config.Config {
|
||||
t.Helper()
|
||||
|
||||
c, err := config.LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -84,6 +90,8 @@ func testConfig(t *testing.T, name string) *config.Config {
|
|||
}
|
||||
|
||||
func testModule(t *testing.T, name string) *module.Tree {
|
||||
t.Helper()
|
||||
|
||||
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -100,6 +108,8 @@ func testModule(t *testing.T, name string) *module.Tree {
|
|||
// testModuleInline takes a map of path -> config strings and yields a config
|
||||
// structure with those files loaded from disk
|
||||
func testModuleInline(t *testing.T, config map[string]string) *module.Tree {
|
||||
t.Helper()
|
||||
|
||||
cfgPath, err := ioutil.TempDir("", "tf-test")
|
||||
if err != nil {
|
||||
t.Errorf("Error creating temporary directory for config: %s", err)
|
||||
|
@ -146,6 +156,8 @@ func testModuleInline(t *testing.T, config map[string]string) *module.Tree {
|
|||
}
|
||||
|
||||
func testStringMatch(t *testing.T, s fmt.Stringer, expected string) {
|
||||
t.Helper()
|
||||
|
||||
actual := strings.TrimSpace(s.String())
|
||||
expected = strings.TrimSpace(expected)
|
||||
if actual != expected {
|
||||
|
|
|
@ -8,17 +8,17 @@ import (
|
|||
"github.com/hashicorp/terraform/config/module"
|
||||
)
|
||||
|
||||
// checkRequiredVersion verifies that any version requirements specified by
|
||||
// CheckRequiredVersion verifies that any version requirements specified by
|
||||
// the configuration are met.
|
||||
//
|
||||
// This checks the root module as well as any additional version requirements
|
||||
// from child modules.
|
||||
//
|
||||
// This is tested in context_test.go.
|
||||
func checkRequiredVersion(m *module.Tree) error {
|
||||
func CheckRequiredVersion(m *module.Tree) error {
|
||||
// Check any children
|
||||
for _, c := range m.Children() {
|
||||
if err := checkRequiredVersion(c); err != nil {
|
||||
if err := CheckRequiredVersion(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,6 +110,12 @@ This special argument applies to _all_ providers.
|
|||
view the specified version constraints for all providers used in the
|
||||
current configuration.
|
||||
|
||||
When `terraform init` is re-run with providers already installed, it will
|
||||
use an already-installed provider that meets the constraints in preference
|
||||
to downloading a new version. To upgrade to the latest acceptable version
|
||||
of each provider, run `terraform init -upgrade`. This command also upgrades
|
||||
to the latest versions of all Terraform modules.
|
||||
|
||||
## Multiple Provider Instances
|
||||
|
||||
You can define multiple instances of the same provider in order to support
|
||||
|
|
|
@ -84,7 +84,7 @@ Additionally, because these map directly to variables, module configuration can
|
|||
|
||||
## Outputs
|
||||
|
||||
Modules can also specify their own [outputs](/docs/configuration/outputs.html). These outputs can be referenced in other places in your configuration, for example:
|
||||
Modules encapsulate their resources. A resource in one module cannot directly depend on resources or attributes in other modules, unless those are exported through [outputs](/docs/configuration/outputs.html). These outputs can be referenced in other places in your configuration, for example:
|
||||
|
||||
```hcl
|
||||
resource "aws_instance" "client" {
|
||||
|
|
Loading…
Reference in New Issue