2014-08-15 04:55:47 +02:00
|
|
|
package schema
|
|
|
|
|
|
|
|
import (
|
2015-01-27 21:23:49 +01:00
|
|
|
"bytes"
|
2017-05-28 18:48:15 +02:00
|
|
|
"errors"
|
2015-01-27 21:23:49 +01:00
|
|
|
"fmt"
|
2015-01-16 19:54:43 +01:00
|
|
|
"os"
|
2014-08-15 04:55:47 +02:00
|
|
|
"reflect"
|
2016-11-05 00:51:26 +01:00
|
|
|
"sort"
|
2015-04-30 22:20:33 +02:00
|
|
|
"strconv"
|
2017-03-24 15:20:23 +01:00
|
|
|
"strings"
|
2014-08-15 04:55:47 +02:00
|
|
|
"testing"
|
|
|
|
|
2019-08-07 01:58:58 +02:00
|
|
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
2015-01-27 21:23:49 +01:00
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
2014-08-15 04:55:47 +02:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
)
|
|
|
|
|
2015-01-16 19:54:43 +01:00
|
|
|
func TestEnvDefaultFunc(t *testing.T) {
|
|
|
|
key := "TF_TEST_ENV_DEFAULT_FUNC"
|
|
|
|
defer os.Unsetenv(key)
|
|
|
|
|
|
|
|
f := EnvDefaultFunc(key, "42")
|
|
|
|
if err := os.Setenv(key, "foo"); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual, err := f()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if actual != "foo" {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Unsetenv(key); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual, err = f()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if actual != "42" {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-22 21:30:27 +01:00
|
|
|
func TestMultiEnvDefaultFunc(t *testing.T) {
|
|
|
|
keys := []string{
|
|
|
|
"TF_TEST_MULTI_ENV_DEFAULT_FUNC1",
|
|
|
|
"TF_TEST_MULTI_ENV_DEFAULT_FUNC2",
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
for _, k := range keys {
|
|
|
|
os.Unsetenv(k)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-01-22 21:34:48 +01:00
|
|
|
// Test that the first key is returned first
|
2015-01-22 21:30:27 +01:00
|
|
|
f := MultiEnvDefaultFunc(keys, "42")
|
2015-01-22 21:34:48 +01:00
|
|
|
if err := os.Setenv(keys[0], "foo"); err != nil {
|
2015-01-22 21:30:27 +01:00
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual, err := f()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if actual != "foo" {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
|
2015-01-22 21:34:48 +01:00
|
|
|
if err := os.Unsetenv(keys[0]); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that the second key is returned if the first one is empty
|
|
|
|
f = MultiEnvDefaultFunc(keys, "42")
|
|
|
|
if err := os.Setenv(keys[1], "foo"); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual, err = f()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if actual != "foo" {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
|
2015-01-22 21:30:27 +01:00
|
|
|
if err := os.Unsetenv(keys[1]); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-01-22 21:34:48 +01:00
|
|
|
// Test that the default value is returned when no keys are set
|
2015-01-22 21:30:27 +01:00
|
|
|
actual, err = f()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if actual != "42" {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-09 03:02:19 +01:00
|
|
|
func TestValueType_Zero(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Type ValueType
|
|
|
|
Value interface{}
|
|
|
|
}{
|
|
|
|
{TypeBool, false},
|
|
|
|
{TypeInt, 0},
|
2015-01-11 00:57:06 +01:00
|
|
|
{TypeFloat, 0.0},
|
2015-01-09 03:02:19 +01:00
|
|
|
{TypeString, ""},
|
|
|
|
{TypeList, []interface{}{}},
|
|
|
|
{TypeMap, map[string]interface{}{}},
|
2015-02-18 01:58:47 +01:00
|
|
|
{TypeSet, new(Set)},
|
2015-01-09 03:02:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
|
|
|
actual := tc.Type.Zero()
|
|
|
|
if !reflect.DeepEqual(actual, tc.Value) {
|
|
|
|
t.Fatalf("%d: %#v != %#v", i, actual, tc.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-15 04:55:47 +02:00
|
|
|
func TestSchemaMap_Diff(t *testing.T) {
|
2016-11-09 02:17:44 +01:00
|
|
|
cases := []struct {
|
2019-07-31 06:05:09 +02:00
|
|
|
Name string
|
|
|
|
Schema map[string]*Schema
|
|
|
|
State *terraform.InstanceState
|
|
|
|
Config map[string]interface{}
|
|
|
|
CustomizeDiff CustomizeDiffFunc
|
|
|
|
Diff *terraform.InstanceDiff
|
|
|
|
Err bool
|
2014-08-15 04:55:47 +02:00
|
|
|
}{
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2014-08-15 04:55:47 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 04:55:47 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2014-08-15 04:55:47 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 04:55:47 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2014-08-22 21:18:08 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-22 21:18:08 +02:00
|
|
|
ID: "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Computed, but set in config",
|
2014-10-21 08:52:22 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "foo",
|
|
|
|
New: "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Default",
|
2014-09-10 06:17:29 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-09-10 06:17:29 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "DefaultFunc, value",
|
2014-09-10 06:33:08 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return "foo", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-09-10 06:33:08 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "DefaultFunc, configuration set",
|
2014-09-10 06:33:08 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return "foo", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-09-10 06:33:08 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "String with StateFunc",
|
2014-08-22 17:45:54 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
StateFunc: func(a interface{}) string {
|
|
|
|
return a.(string) + "!"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-22 17:45:54 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
2014-08-22 17:57:44 +02:00
|
|
|
Old: "",
|
|
|
|
New: "foo!",
|
|
|
|
NewExtra: "foo",
|
2014-08-22 17:45:54 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "StateFunc not called with nil value",
|
2015-11-20 21:02:20 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
StateFunc: func(a interface{}) string {
|
|
|
|
t.Fatalf("should not get here!")
|
|
|
|
return ""
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Variable computed",
|
2014-08-31 02:03:01 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"availability_zone": hcl2shim.UnknownVariableValue,
|
2014-08-31 02:03:01 +02:00
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-31 02:03:01 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
helper/schema,terraform: handle computed primtives in diffs
Fixes #3309
There are two primary changes, one to how helper/schema creates diffs
and one to how Terraform compares diffs. Both require careful
understanding.
== 1. helper/schema Changes
helper/schema, given any primitive field (string, int, bool, etc.)
_used to_ create a basic diff when given a computed new value (i.e. from
an unkown interpolation). This would put in the plan that the old value
is whatever the old value was, and the new value was the actual
interpolation. For example, from #3309, the diff showed the following:
```
~ module.test.aws_eip.test-instance.0
instance: "<INSTANCE ID>" => "${element(aws_instance.test-instance.*.id, count.index)}"
```
Then, when running `apply`, the diff would be realized and you would get
a diff mismatch error because it would realize the final value is the
same and remove it from the diff.
**The change:** `helper/schema` now marks unknown primitive values with
`NewComputed` set to true. Semantically this is correct for the diff to
have this information.
== 2. Terraform Diff.Same Changes
Next, the way Terraform compares diffs needed to be updated
Specifically, the case where the diff from the plan had a NewComputed
primitive and the diff from the apply _no longer has that value_. This
is possible if the computed value ended up being the same as the old
value. This is allowed to pass through.
Together, these fix #3309.
2016-10-26 04:36:59 +02:00
|
|
|
Old: "",
|
2019-07-31 06:05:09 +02:00
|
|
|
New: hcl2shim.UnknownVariableValue,
|
helper/schema,terraform: handle computed primtives in diffs
Fixes #3309
There are two primary changes, one to how helper/schema creates diffs
and one to how Terraform compares diffs. Both require careful
understanding.
== 1. helper/schema Changes
helper/schema, given any primitive field (string, int, bool, etc.)
_used to_ create a basic diff when given a computed new value (i.e. from
an unkown interpolation). This would put in the plan that the old value
is whatever the old value was, and the new value was the actual
interpolation. For example, from #3309, the diff showed the following:
```
~ module.test.aws_eip.test-instance.0
instance: "<INSTANCE ID>" => "${element(aws_instance.test-instance.*.id, count.index)}"
```
Then, when running `apply`, the diff would be realized and you would get
a diff mismatch error because it would realize the final value is the
same and remove it from the diff.
**The change:** `helper/schema` now marks unknown primitive values with
`NewComputed` set to true. Semantically this is correct for the diff to
have this information.
== 2. Terraform Diff.Same Changes
Next, the way Terraform compares diffs needed to be updated
Specifically, the case where the diff from the plan had a NewComputed
primitive and the diff from the apply _no longer has that value_. This
is possible if the computed value ended up being the same as the old
value. This is allowed to pass through.
Together, these fix #3309.
2016-10-26 04:36:59 +02:00
|
|
|
NewComputed: true,
|
2014-08-31 02:03:01 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Int decode",
|
2014-08-15 05:02:52 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"port": 27,
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 05:02:52 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"port": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "27",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "bool decode",
|
2014-08-15 05:02:52 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"port": false,
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 05:02:52 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"port": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
helper/schema: Normalize bools to "true"/"false" in diffs
For a long time now, the diff logic has relied on the behavior of
`mapstructure.WeakDecode` to determine how various primitives are
converted into strings. The `schema.DiffString` function is used for
all primitive field types: TypeBool, TypeInt, TypeFloat, and TypeString.
The `mapstructure` library's string representation of booleans is "0"
and "1", which differs from `strconv.FormatBool`'s "false" and "true"
(which is used in writing out boolean fields to the state).
Because of this difference, diffs have long had the potential for
cosmetically odd but semantically neutral output like:
"true" => "1"
"false" => "0"
So long as `mapstructure.Decode` or `strconv.ParseBool` are used to
interpret these strings, there's no functional problem.
We had our first clear functional problem with #6005 and friends, where
users noticed diffs like the above showing up unexpectedly and causing
troubles when `ignore_changes` was in play.
This particular bug occurs down in Terraform core's EvalIgnoreChanges.
There, the diff is modified to account for ignored attributes, and
special logic attempts to handle properly the situation where the
ignored attribute was going to trigger a resource replacement. That
logic relies on the string representations of the Old and New fields in
the diff to be the same so that it filters properly.
So therefore, we now get a bug when a diff includes `Old: "0", New:
"false"` since the strings do not match, and `ignore_changes` is not
properly handled.
Here, we introduce `TypeBool`-specific normalizing into `finalizeDiff`.
I spiked out a full `diffBool` function, but figuring out which pieces
of `diffString` to duplicate there got hairy. This seemed like a simpler
and more direct solution.
Fixes #6005 (and potentially others!)
2016-05-05 16:00:58 +02:00
|
|
|
New: "false",
|
2014-08-15 05:02:52 +02:00
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-15 08:17:53 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Bool",
|
2014-10-20 23:08:56 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"delete": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"delete": "false",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "List decode",
|
2014-08-15 08:17:53 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{1, 2, 5},
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 08:17:53 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
2014-08-19 06:22:27 +02:00
|
|
|
Old: "0",
|
2014-08-15 08:17:53 +02:00
|
|
|
New: "3",
|
|
|
|
},
|
|
|
|
"ports.0": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "2",
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-12-23 02:55:23 +01:00
|
|
|
{
|
|
|
|
Name: "List decode with promotion",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
PromoteSingle: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": "5",
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ports.0": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
Name: "List decode with promotion with list",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
PromoteSingle: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{"5"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ports.0": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2014-10-10 01:31:24 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ports": []interface{}{1, 2, 5},
|
2014-10-10 01:31:24 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "3",
|
|
|
|
},
|
|
|
|
"ports.0": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "2",
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2014-10-10 04:09:06 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5},
|
2014-10-10 04:09:06 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
2014-10-10 23:50:35 +02:00
|
|
|
Old: "0",
|
|
|
|
New: "",
|
2014-10-10 04:09:06 +02:00
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2014-08-15 08:17:53 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-15 08:17:53 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "3",
|
|
|
|
"ports.0": "1",
|
|
|
|
"ports.1": "2",
|
|
|
|
"ports.2": "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{1, 2, 5},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "",
|
2014-08-15 08:17:53 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-15 08:17:53 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "2",
|
|
|
|
"ports.0": "1",
|
|
|
|
"ports.1": "2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{1, 2, 5},
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 08:17:53 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "3",
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-15 08:32:20 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "",
|
2014-08-15 08:32:20 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{1, 2, 5},
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 08:32:20 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
2014-08-19 06:22:27 +02:00
|
|
|
Old: "0",
|
2014-08-15 08:32:20 +02:00
|
|
|
New: "3",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"ports.0": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "2",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-15 19:25:25 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "",
|
2014-08-19 06:22:27 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-19 06:22:27 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
2014-08-21 02:51:27 +02:00
|
|
|
Old: "",
|
2014-08-19 06:22:27 +02:00
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2017-02-23 19:03:59 +01:00
|
|
|
{
|
|
|
|
Name: "List with computed set",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"config": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
MinItems: 1,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"name": {
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"rules": {
|
|
|
|
Type: TypeSet,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
Set: HashString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"config": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"name": "hello",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"config.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"config.0.name": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "hello",
|
|
|
|
},
|
|
|
|
|
|
|
|
"config.0.rules.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-08-15 19:25:25 +02:00
|
|
|
Schema: map[string]*Schema{
|
2014-08-21 00:45:34 +02:00
|
|
|
"ports": &Schema{
|
2014-08-21 02:51:27 +02:00
|
|
|
Type: TypeSet,
|
2014-08-15 19:25:25 +02:00
|
|
|
Required: true,
|
2014-08-21 00:45:34 +02:00
|
|
|
Elem: &Schema{Type: TypeInt},
|
2014-08-21 02:51:27 +02:00
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
2014-08-15 19:25:25 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2014-08-21 00:45:34 +02:00
|
|
|
"ports": []interface{}{5, 2, 1},
|
2014-08-15 19:25:25 +02:00
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-15 19:25:25 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
2014-08-21 00:45:34 +02:00
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
2014-08-19 06:22:27 +02:00
|
|
|
Old: "0",
|
2014-08-21 00:45:34 +02:00
|
|
|
New: "3",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
2014-08-21 00:45:34 +02:00
|
|
|
Old: "",
|
2014-08-15 19:25:25 +02:00
|
|
|
New: "1",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
2014-08-15 19:25:25 +02:00
|
|
|
Old: "",
|
2014-08-21 00:45:34 +02:00
|
|
|
New: "2",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.5": &terraform.ResourceAttrDiff{
|
2014-08-21 00:45:34 +02:00
|
|
|
Old: "",
|
|
|
|
New: "5",
|
2014-08-15 19:25:25 +02:00
|
|
|
},
|
2014-10-10 18:13:04 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-10-11 00:46:24 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Computed: true,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-10-11 00:58:38 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-10-10 18:13:04 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ports": []interface{}{"2", "5", 1},
|
2014-10-10 18:13:04 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "3",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
2014-10-10 18:13:04 +02:00
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
2014-10-10 18:13:04 +02:00
|
|
|
Old: "",
|
|
|
|
New: "2",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.5": &terraform.ResourceAttrDiff{
|
2014-10-10 18:13:04 +02:00
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-10-10 18:13:04 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"},
|
2014-10-10 18:13:04 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
2015-01-15 00:35:40 +01:00
|
|
|
Old: "",
|
2014-10-10 23:50:35 +02:00
|
|
|
New: "",
|
2014-10-10 18:13:04 +02:00
|
|
|
NewComputed: true,
|
|
|
|
},
|
2014-08-15 19:25:25 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-15 19:39:40 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-08-21 06:02:42 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-21 06:02:42 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "2",
|
|
|
|
"ports.1": "1",
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.2": "2",
|
2014-08-21 06:02:42 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{5, 2, 1},
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-21 06:02:42 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "3",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
2014-10-21 19:49:27 +02:00
|
|
|
Old: "1",
|
|
|
|
New: "1",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
2014-10-21 19:49:27 +02:00
|
|
|
Old: "2",
|
|
|
|
New: "2",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.5": &terraform.ResourceAttrDiff{
|
2014-08-21 06:02:42 +02:00
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-08-21 06:02:42 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-21 06:02:42 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "2",
|
|
|
|
"ports.1": "1",
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.2": "2",
|
2014-08-21 06:02:42 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-21 06:02:42 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "0",
|
|
|
|
},
|
2015-06-26 06:52:49 +02:00
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "1",
|
|
|
|
New: "0",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "0",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
2014-08-21 06:02:42 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-08-28 00:03:42 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
2014-12-12 23:21:20 +01:00
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
2014-08-28 00:03:42 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-28 00:03:42 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
"ports.#": "1",
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.80": "80",
|
2014-08-28 00:03:42 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
2014-10-11 19:40:54 +02:00
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-10-11 19:40:54 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
ps := m["ports"].([]interface{})
|
|
|
|
result := 0
|
|
|
|
for _, p := range ps {
|
|
|
|
result += p.(int)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
2014-12-12 23:21:20 +01:00
|
|
|
"ingress.#": "2",
|
|
|
|
"ingress.80.ports.#": "1",
|
|
|
|
"ingress.80.ports.0": "80",
|
|
|
|
"ingress.443.ports.#": "1",
|
|
|
|
"ingress.443.ports.0": "443",
|
2014-10-11 19:40:54 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ingress": []interface{}{
|
2014-10-11 19:40:54 +02:00
|
|
|
map[string]interface{}{
|
|
|
|
"ports": []interface{}{443},
|
|
|
|
},
|
|
|
|
map[string]interface{}{
|
|
|
|
"ports": []interface{}{80},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2014-08-28 00:03:42 +02:00
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "List of structure decode",
|
2014-08-15 19:39:40 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2014-08-21 00:45:34 +02:00
|
|
|
"ingress": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"from": 8080,
|
2014-08-15 19:39:40 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-21 00:45:34 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ingress.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ingress.0.from": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "8080",
|
|
|
|
},
|
2014-08-15 19:39:40 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-08-21 00:45:34 +02:00
|
|
|
Err: false,
|
2014-08-15 19:39:40 +02:00
|
|
|
},
|
2014-08-16 18:49:22 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "ComputedWhen",
|
2014-08-16 18:49:22 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
ComputedWhen: []string{"port"},
|
|
|
|
},
|
|
|
|
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-16 18:49:22 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
"port": "80",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"port": 80,
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "",
|
2014-08-16 18:49:22 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
ComputedWhen: []string{"port"},
|
|
|
|
},
|
|
|
|
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-16 18:49:22 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"port": "80",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"port": 80,
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-16 18:49:22 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-16 22:32:21 +02:00
|
|
|
|
|
|
|
/* TODO
|
|
|
|
{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
ComputedWhen: []string{"port"},
|
|
|
|
},
|
|
|
|
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-16 22:32:21 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
"port": "80",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"port": 8080,
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.ResourceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "foo",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
"port": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "80",
|
|
|
|
New: "8080",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
*/
|
2014-08-19 00:07:09 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Maps",
|
2014-10-09 02:35:14 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"config_vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"config_vars": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"config_vars.%": &terraform.ResourceAttrDiff{
|
2014-12-16 02:35:16 +01:00
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
|
2014-10-09 02:35:14 +02:00
|
|
|
"config_vars.bar": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Maps",
|
2014-10-09 03:25:31 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"config_vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"config_vars.foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"config_vars": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"config_vars.foo": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
"config_vars.bar": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Maps",
|
2014-10-21 08:52:22 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"vars.foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"vars": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"vars.foo": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
New: "",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
"vars.bar": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Maps",
|
2014-10-20 23:23:06 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"vars.foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Maps",
|
2014-08-19 00:07:09 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"config_vars": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Schema{Type: TypeMap},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-19 00:07:09 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"config_vars.#": "1",
|
|
|
|
"config_vars.0.foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"config_vars": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"bar": "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-19 00:07:09 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"config_vars.0.foo": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
"config_vars.0.bar": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-19 01:54:30 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Maps",
|
2014-08-19 01:54:30 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"config_vars": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Schema{Type: TypeMap},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-19 01:54:30 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"config_vars.#": "1",
|
|
|
|
"config_vars.0.foo": "bar",
|
|
|
|
"config_vars.0.bar": "baz",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-19 01:54:30 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"config_vars.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "1",
|
|
|
|
New: "0",
|
|
|
|
},
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"config_vars.0.%": &terraform.ResourceAttrDiff{
|
2014-12-16 02:35:16 +01:00
|
|
|
Old: "2",
|
|
|
|
New: "0",
|
|
|
|
},
|
2014-08-19 01:54:30 +02:00
|
|
|
"config_vars.0.foo": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
"config_vars.0.bar": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "baz",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-28 00:26:15 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "ForceNews",
|
2014-08-28 00:26:15 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"address": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-28 00:26:15 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
"address": "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-28 00:26:15 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
New: "foo",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"address": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "foo",
|
|
|
|
New: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-08-28 00:26:15 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
2014-12-12 23:21:20 +01:00
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
2014-08-28 00:26:15 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-09-17 02:07:13 +02:00
|
|
|
State: &terraform.InstanceState{
|
2014-08-28 00:26:15 +02:00
|
|
|
Attributes: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
"ports.#": "1",
|
2014-12-12 23:21:20 +01:00
|
|
|
"ports.80": "80",
|
2014-08-28 00:26:15 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2014-09-18 01:33:24 +02:00
|
|
|
Diff: &terraform.InstanceDiff{
|
2014-08-28 00:26:15 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "bar",
|
|
|
|
New: "foo",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "1",
|
|
|
|
New: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-12-12 15:24:29 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-12-12 15:24:29 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"instances": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Set: func(v interface{}) int {
|
2014-12-12 15:42:01 +01:00
|
|
|
return len(v.(string))
|
2014-12-12 15:24:29 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"instances.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"instances": []interface{}{hcl2shim.UnknownVariableValue},
|
2014-12-12 15:24:29 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
2014-12-12 23:21:20 +01:00
|
|
|
"instances.#": &terraform.ResourceAttrDiff{
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-12-12 23:21:20 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"route": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"index": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"gateway": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
return m["index"].(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"route": []interface{}{
|
2014-12-12 23:21:20 +01:00
|
|
|
map[string]interface{}{
|
|
|
|
"index": "1",
|
2019-07-31 06:05:09 +02:00
|
|
|
"gateway": hcl2shim.UnknownVariableValue,
|
2014-12-12 23:21:20 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"route.#": &terraform.ResourceAttrDiff{
|
2014-12-12 15:24:29 +01:00
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
2014-12-12 23:21:20 +01:00
|
|
|
"route.~1.index": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"route.~1.gateway": &terraform.ResourceAttrDiff{
|
helper/schema,terraform: handle computed primtives in diffs
Fixes #3309
There are two primary changes, one to how helper/schema creates diffs
and one to how Terraform compares diffs. Both require careful
understanding.
== 1. helper/schema Changes
helper/schema, given any primitive field (string, int, bool, etc.)
_used to_ create a basic diff when given a computed new value (i.e. from
an unkown interpolation). This would put in the plan that the old value
is whatever the old value was, and the new value was the actual
interpolation. For example, from #3309, the diff showed the following:
```
~ module.test.aws_eip.test-instance.0
instance: "<INSTANCE ID>" => "${element(aws_instance.test-instance.*.id, count.index)}"
```
Then, when running `apply`, the diff would be realized and you would get
a diff mismatch error because it would realize the final value is the
same and remove it from the diff.
**The change:** `helper/schema` now marks unknown primitive values with
`NewComputed` set to true. Semantically this is correct for the diff to
have this information.
== 2. Terraform Diff.Same Changes
Next, the way Terraform compares diffs needed to be updated
Specifically, the case where the diff from the plan had a NewComputed
primitive and the diff from the apply _no longer has that value_. This
is possible if the computed value ended up being the same as the old
value. This is allowed to pass through.
Together, these fix #3309.
2016-10-26 04:36:59 +02:00
|
|
|
Old: "",
|
2019-07-31 06:05:09 +02:00
|
|
|
New: hcl2shim.UnknownVariableValue,
|
helper/schema,terraform: handle computed primtives in diffs
Fixes #3309
There are two primary changes, one to how helper/schema creates diffs
and one to how Terraform compares diffs. Both require careful
understanding.
== 1. helper/schema Changes
helper/schema, given any primitive field (string, int, bool, etc.)
_used to_ create a basic diff when given a computed new value (i.e. from
an unkown interpolation). This would put in the plan that the old value
is whatever the old value was, and the new value was the actual
interpolation. For example, from #3309, the diff showed the following:
```
~ module.test.aws_eip.test-instance.0
instance: "<INSTANCE ID>" => "${element(aws_instance.test-instance.*.id, count.index)}"
```
Then, when running `apply`, the diff would be realized and you would get
a diff mismatch error because it would realize the final value is the
same and remove it from the diff.
**The change:** `helper/schema` now marks unknown primitive values with
`NewComputed` set to true. Semantically this is correct for the diff to
have this information.
== 2. Terraform Diff.Same Changes
Next, the way Terraform compares diffs needed to be updated
Specifically, the case where the diff from the plan had a NewComputed
primitive and the diff from the apply _no longer has that value_. This
is possible if the computed value ended up being the same as the old
value. This is allowed to pass through.
Together, these fix #3309.
2016-10-26 04:36:59 +02:00
|
|
|
NewComputed: true,
|
2014-12-12 23:21:20 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2014-12-12 15:24:29 +01:00
|
|
|
|
2014-12-12 23:21:20 +01:00
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set",
|
2014-12-12 23:21:20 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"route": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"index": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"gateway": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
return m["index"].(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"route": []interface{}{
|
2014-12-12 23:21:20 +01:00
|
|
|
map[string]interface{}{
|
|
|
|
"index": "1",
|
|
|
|
"gateway": []interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
hcl2shim.UnknownVariableValue,
|
2014-12-12 23:21:20 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"route.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"route.~1.index": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"route.~1.gateway.#": &terraform.ResourceAttrDiff{
|
2014-12-12 15:24:29 +01:00
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-12-16 02:35:16 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Computed maps",
|
2014-12-16 02:35:16 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"vars.%": &terraform.ResourceAttrDiff{
|
2014-12-16 02:35:16 +01:00
|
|
|
Old: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-12-16 18:05:16 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Computed maps",
|
2014-12-16 18:05:16 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"vars.%": "0",
|
2014-12-16 18:05:16 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"vars": map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"bar": hcl2shim.UnknownVariableValue,
|
2014-12-16 18:05:16 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"vars.%": &terraform.ResourceAttrDiff{
|
2014-12-16 18:05:16 +01:00
|
|
|
Old: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-12-17 00:56:40 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: " - Empty",
|
2014-12-17 00:56:40 +01:00
|
|
|
Schema: map[string]*Schema{},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-01-11 21:51:48 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Float",
|
2015-01-11 21:51:48 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"some_threshold": &Schema{
|
|
|
|
Type: TypeFloat,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"some_threshold": "567.8",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"some_threshold": 12.34,
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
2015-01-14 18:32:03 +01:00
|
|
|
"some_threshold": &terraform.ResourceAttrDiff{
|
2015-01-11 21:51:48 +01:00
|
|
|
Old: "567.8",
|
|
|
|
New: "12.34",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-01-27 21:23:49 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "https://github.com/hashicorp/terraform/issues/824",
|
2015-01-27 21:23:49 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"block_device": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"device_name": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"delete_on_termination": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
|
|
|
buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
|
|
|
|
return hashcode.String(buf.String())
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
2018-09-09 19:18:08 +02:00
|
|
|
"block_device.#": "2",
|
2015-01-27 21:23:49 +01:00
|
|
|
"block_device.616397234.delete_on_termination": "true",
|
|
|
|
"block_device.616397234.device_name": "/dev/sda1",
|
|
|
|
"block_device.2801811477.delete_on_termination": "true",
|
|
|
|
"block_device.2801811477.device_name": "/dev/sdx",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"block_device": []interface{}{
|
2015-01-27 21:23:49 +01:00
|
|
|
map[string]interface{}{
|
|
|
|
"device_name": "/dev/sda1",
|
|
|
|
},
|
|
|
|
map[string]interface{}{
|
|
|
|
"device_name": "/dev/sdx",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-17 20:10:45 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Zero value in state shouldn't result in diff",
|
2015-02-17 20:10:45 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"port": "false",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-17 20:12:45 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Same as prev, but for sets",
|
2015-02-17 20:12:45 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"route": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"index": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"gateway": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
return m["index"].(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"route.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-17 22:15:30 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "A set computed element shouldn't cause a diff",
|
2015-02-17 22:15:30 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"active": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"active": "true",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-17 23:45:18 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "An empty set should show up in the diff",
|
2015-02-17 23:45:18 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"instances": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return len(v.(string))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-02-18 21:54:08 +01:00
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"instances.#": "1",
|
|
|
|
"instances.3": "foo",
|
|
|
|
},
|
|
|
|
},
|
2015-02-17 23:45:18 +01:00
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"instances.#": &terraform.ResourceAttrDiff{
|
2015-02-18 21:54:08 +01:00
|
|
|
Old: "1",
|
2015-02-17 23:45:18 +01:00
|
|
|
New: "0",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
2015-06-26 06:52:49 +02:00
|
|
|
"instances.3": &terraform.ResourceAttrDiff{
|
2016-10-28 21:35:26 +02:00
|
|
|
Old: "foo",
|
|
|
|
New: "",
|
|
|
|
NewRemoved: true,
|
|
|
|
RequiresNew: true,
|
2015-06-26 06:52:49 +02:00
|
|
|
},
|
2015-02-17 23:45:18 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-18 00:22:45 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Map with empty value",
|
2015-02-18 00:22:45 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"vars": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"vars": map[string]interface{}{
|
|
|
|
"foo": "",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
core: Use .% instead of .# for maps in state
The flatmapped representation of state prior to this commit encoded maps
and lists (and therefore by extension, sets) with a key corresponding to
the number of elements, or the unknown variable indicator under a .# key
and then individual items. For example, the list ["a", "b", "c"] would
have been encoded as:
listname.# = 3
listname.0 = "a"
listname.1 = "b"
listname.2 = "c"
And the map {"key1": "value1", "key2", "value2"} would have been encoded
as:
mapname.# = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
Sets use the hash code as the key - for example a set with a (fictional)
hashcode calculation may look like:
setname.# = 2
setname.12312512 = "value1"
setname.56345233 = "value2"
Prior to the work done to extend the type system, this was sufficient
since the internal representation of these was effectively the same.
However, following the separation of maps and lists into distinct
first-class types, this encoding presents a problem: given a state file,
it is impossible to tell the encoding of an empty list and an empty map
apart. This presents problems for the type checker during interpolation,
as many interpolation functions will operate on only one of these two
structures.
This commit therefore changes the representation in state of maps to use
a "%" as the key for the number of elements. Consequently the map above
will now be encoded as:
mapname.% = 2
mapname.key1 = "value1"
mapname.key2 = "value2"
This has the effect of an empty list (or set) now being encoded as:
listname.# = 0
And an empty map now being encoded as:
mapname.% = 0
Therefore we can eliminate some nasty guessing logic from the resource
variable supplier for interpolation, at the cost of having to migrate
state up front (to follow in a subsequent commit).
In order to reduce the number of potential situations in which resources
would be "forced new", we continue to accept "#" as the count key when
reading maps via helper/schema. There is no situation under which we can
allow "#" as an actual map key in any case, as it would not be
distinguishable from a list or set in state.
2016-06-05 10:34:43 +02:00
|
|
|
"vars.%": &terraform.ResourceAttrDiff{
|
2015-02-18 00:22:45 +01:00
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"vars.foo": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-18 21:54:08 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Unset bool, not in state",
|
2015-02-18 21:54:08 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"force": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Unset set, not in state",
|
2015-02-18 21:54:08 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"metadata_keys": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(interface{}) int { return 0 },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-18 23:10:12 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Unset list in state, should not show up computed",
|
2015-02-18 23:10:12 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"metadata_keys": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"metadata_keys.#": "0",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-02-28 06:51:14 +01:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
2019-07-31 06:05:09 +02:00
|
|
|
Name: "Set element computed element",
|
2015-02-28 06:51:14 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ports": []interface{}{1, hcl2shim.UnknownVariableValue},
|
2015-02-28 06:51:14 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-04-21 22:13:03 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Computed map without config that's known to be empty does not generate diff",
|
2015-04-21 22:13:03 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"tags": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
2016-06-10 17:07:17 +02:00
|
|
|
"tags.%": "0",
|
2015-04-21 22:13:03 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-04-23 17:20:54 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set with hyphen keys",
|
2015-04-23 17:20:54 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"route": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"index": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"gateway-name": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
return m["index"].(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"route": []interface{}{
|
2015-04-23 17:20:54 +02:00
|
|
|
map[string]interface{}{
|
|
|
|
"index": "1",
|
|
|
|
"gateway-name": "hello",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"route.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"route.1.index": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"route.1.gateway-name": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "hello",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-04-30 22:20:33 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: ": StateFunc in nested set (#1759)",
|
2015-04-30 22:20:33 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"service_account": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"scopes": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
StateFunc: func(v interface{}) string {
|
|
|
|
return v.(string) + "!"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
i, err := strconv.Atoi(v.(string))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
return i
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"service_account": []interface{}{
|
|
|
|
map[string]interface{}{
|
2015-04-30 22:20:33 +02:00
|
|
|
"scopes": []interface{}{"123"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"service_account.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"service_account.0.scopes.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"service_account.0.scopes.123": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "123!",
|
|
|
|
NewExtra: "123",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-06-26 06:52:49 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Removing set elements",
|
2015-06-26 06:52:49 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"instances": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return len(v.(string))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"instances.#": "2",
|
|
|
|
"instances.3": "333",
|
|
|
|
"instances.2": "22",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"instances": []interface{}{"333", "4444"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"instances.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "2",
|
|
|
|
},
|
|
|
|
"instances.2": &terraform.ResourceAttrDiff{
|
2016-10-28 21:35:26 +02:00
|
|
|
Old: "22",
|
|
|
|
New: "",
|
|
|
|
NewRemoved: true,
|
|
|
|
RequiresNew: true,
|
2015-06-26 06:52:49 +02:00
|
|
|
},
|
|
|
|
"instances.3": &terraform.ResourceAttrDiff{
|
2016-10-26 02:56:45 +02:00
|
|
|
Old: "333",
|
|
|
|
New: "333",
|
2015-06-26 06:52:49 +02:00
|
|
|
},
|
|
|
|
"instances.4": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "4444",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
helper/schema: Normalize bools to "true"/"false" in diffs
For a long time now, the diff logic has relied on the behavior of
`mapstructure.WeakDecode` to determine how various primitives are
converted into strings. The `schema.DiffString` function is used for
all primitive field types: TypeBool, TypeInt, TypeFloat, and TypeString.
The `mapstructure` library's string representation of booleans is "0"
and "1", which differs from `strconv.FormatBool`'s "false" and "true"
(which is used in writing out boolean fields to the state).
Because of this difference, diffs have long had the potential for
cosmetically odd but semantically neutral output like:
"true" => "1"
"false" => "0"
So long as `mapstructure.Decode` or `strconv.ParseBool` are used to
interpret these strings, there's no functional problem.
We had our first clear functional problem with #6005 and friends, where
users noticed diffs like the above showing up unexpectedly and causing
troubles when `ignore_changes` was in play.
This particular bug occurs down in Terraform core's EvalIgnoreChanges.
There, the diff is modified to account for ignored attributes, and
special logic attempts to handle properly the situation where the
ignored attribute was going to trigger a resource replacement. That
logic relies on the string representations of the Old and New fields in
the diff to be the same so that it filters properly.
So therefore, we now get a bug when a diff includes `Old: "0", New:
"false"` since the strings do not match, and `ignore_changes` is not
properly handled.
Here, we introduce `TypeBool`-specific normalizing into `finalizeDiff`.
I spiked out a full `diffBool` function, but figuring out which pieces
of `diffString` to duplicate there got hairy. This seemed like a simpler
and more direct solution.
Fixes #6005 (and potentially others!)
2016-05-05 16:00:58 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Bools can be set with 0/1 in config, still get true/false",
|
helper/schema: Normalize bools to "true"/"false" in diffs
For a long time now, the diff logic has relied on the behavior of
`mapstructure.WeakDecode` to determine how various primitives are
converted into strings. The `schema.DiffString` function is used for
all primitive field types: TypeBool, TypeInt, TypeFloat, and TypeString.
The `mapstructure` library's string representation of booleans is "0"
and "1", which differs from `strconv.FormatBool`'s "false" and "true"
(which is used in writing out boolean fields to the state).
Because of this difference, diffs have long had the potential for
cosmetically odd but semantically neutral output like:
"true" => "1"
"false" => "0"
So long as `mapstructure.Decode` or `strconv.ParseBool` are used to
interpret these strings, there's no functional problem.
We had our first clear functional problem with #6005 and friends, where
users noticed diffs like the above showing up unexpectedly and causing
troubles when `ignore_changes` was in play.
This particular bug occurs down in Terraform core's EvalIgnoreChanges.
There, the diff is modified to account for ignored attributes, and
special logic attempts to handle properly the situation where the
ignored attribute was going to trigger a resource replacement. That
logic relies on the string representations of the Old and New fields in
the diff to be the same so that it filters properly.
So therefore, we now get a bug when a diff includes `Old: "0", New:
"false"` since the strings do not match, and `ignore_changes` is not
properly handled.
Here, we introduce `TypeBool`-specific normalizing into `finalizeDiff`.
I spiked out a full `diffBool` function, but figuring out which pieces
of `diffString` to duplicate there got hairy. This seemed like a simpler
and more direct solution.
Fixes #6005 (and potentially others!)
2016-05-05 16:00:58 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"one": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"two": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"three": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"one": "false",
|
|
|
|
"two": "true",
|
|
|
|
"three": "true",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"one": "1",
|
|
|
|
"two": "0",
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"one": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "false",
|
|
|
|
New: "true",
|
|
|
|
},
|
|
|
|
"two": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "true",
|
|
|
|
New: "false",
|
|
|
|
},
|
|
|
|
"three": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "true",
|
|
|
|
New: "false",
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-08-13 00:20:09 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "tainted in state w/ no attr changes is still a replacement",
|
2016-08-13 00:20:09 +02:00
|
|
|
Schema: map[string]*Schema{},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"id": "someid",
|
|
|
|
},
|
|
|
|
Tainted: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{},
|
|
|
|
DestroyTainted: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-10-28 21:35:26 +02:00
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "Set ForceNew only marks the changing element as ForceNew",
|
2016-10-26 02:56:45 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "3",
|
|
|
|
"ports.1": "1",
|
|
|
|
"ports.2": "2",
|
|
|
|
"ports.4": "4",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{5, 2, 1},
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "3",
|
|
|
|
New: "3",
|
|
|
|
},
|
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "1",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "2",
|
|
|
|
},
|
|
|
|
"ports.5": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"ports.4": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "4",
|
|
|
|
New: "0",
|
|
|
|
NewRemoved: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "removed optional items should trigger ForceNew",
|
2016-10-28 21:35:26 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"description": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
ForceNew: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"description": "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"description": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "foo",
|
|
|
|
New: "",
|
|
|
|
RequiresNew: true,
|
|
|
|
NewRemoved: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-11-02 21:25:23 +01:00
|
|
|
|
|
|
|
// GH-7715
|
2016-11-09 02:17:44 +01:00
|
|
|
{
|
|
|
|
Name: "computed value for boolean field",
|
2016-11-02 21:25:23 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
ForceNew: true,
|
|
|
|
Computed: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"foo": hcl2shim.UnknownVariableValue,
|
2016-11-02 21:25:23 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"foo": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "false",
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-11-15 20:02:14 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
Name: "Set ForceNew marks count as ForceNew if computed",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "3",
|
|
|
|
"ports.1": "1",
|
|
|
|
"ports.2": "2",
|
|
|
|
"ports.4": "4",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1},
|
2016-11-15 20:02:14 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "3",
|
|
|
|
New: "",
|
|
|
|
NewComputed: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2017-04-21 23:33:10 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
Name: "List with computed schema and ForceNew",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"config": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"config.#": "2",
|
|
|
|
"config.0": "a",
|
|
|
|
"config.1": "b",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue},
|
2017-04-21 23:33:10 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"config.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "",
|
|
|
|
RequiresNew: true,
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2017-05-28 07:58:44 +02:00
|
|
|
|
|
|
|
{
|
2017-05-31 19:42:41 +02:00
|
|
|
Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema",
|
2017-05-28 18:48:15 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 18:48:15 +02:00
|
|
|
if err := d.SetNew("availability_zone", "bar"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := d.ForceNew("availability_zone"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "bar",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
2018-04-07 22:06:47 +02:00
|
|
|
// NOTE: This case is technically impossible in the current
|
|
|
|
// implementation, because optional+computed values never show up in the
|
|
|
|
// diff. In the event behavior changes this test should ensure that the
|
|
|
|
// intended diff still shows up.
|
|
|
|
Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
|
|
|
if err := d.SetNew("availability_zone", "bar"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := d.ForceNew("availability_zone"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "bar",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
|
2017-05-28 07:58:44 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 07:58:44 +02:00
|
|
|
if err := d.SetNew("availability_zone", "bar"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "bar",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2017-05-28 18:48:15 +02:00
|
|
|
|
|
|
|
{
|
2017-05-31 19:42:41 +02:00
|
|
|
Name: "required field with computed diff added with CustomizeDiff function",
|
2017-05-28 18:48:15 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ami_id": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"instance_id": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ami_id": "foo",
|
|
|
|
},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 18:48:15 +02:00
|
|
|
if err := d.SetNew("instance_id", "bar"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ami_id": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
"instance_id": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
2017-05-31 19:42:41 +02:00
|
|
|
Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
|
2017-05-28 18:48:15 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"ports.#": "3",
|
|
|
|
"ports.1": "1",
|
|
|
|
"ports.2": "2",
|
|
|
|
"ports.4": "4",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": []interface{}{5, 2, 6},
|
|
|
|
},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 18:48:15 +02:00
|
|
|
if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := d.ForceNew("ports"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"ports.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "3",
|
|
|
|
New: "3",
|
|
|
|
},
|
|
|
|
"ports.1": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "1",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"ports.2": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "2",
|
|
|
|
New: "2",
|
|
|
|
},
|
|
|
|
"ports.5": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "5",
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
"ports.4": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "4",
|
|
|
|
New: "0",
|
|
|
|
NewRemoved: true,
|
|
|
|
RequiresNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
2017-05-31 19:42:41 +02:00
|
|
|
Name: "tainted resource does not run CustomizeDiffFunc",
|
2017-05-28 18:48:15 +02:00
|
|
|
Schema: map[string]*Schema{},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"id": "someid",
|
|
|
|
},
|
|
|
|
Tainted: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 18:48:15 +02:00
|
|
|
return errors.New("diff customization should not have run")
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{},
|
|
|
|
DestroyTainted: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
2017-05-31 19:42:41 +02:00
|
|
|
Name: "NewComputed based on a conditional with CustomizeDiffFunc",
|
2017-05-28 18:48:15 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"etag": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"version_id": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"etag": "foo",
|
|
|
|
"version_id": "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"etag": "bar",
|
|
|
|
},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 18:48:15 +02:00
|
|
|
if d.HasChange("etag") {
|
|
|
|
d.SetNewComputed("version_id")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"etag": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "foo",
|
|
|
|
New: "bar",
|
|
|
|
},
|
|
|
|
"version_id": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "1",
|
|
|
|
New: "",
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
helper/schema: Always propagate NewComputed for previously zero value primative type attributes
When the following conditions were met:
* Schema attribute with a primative type (e.g. Type: TypeString) and Computed: true
* Old state of attribute set to zero value for type (e.g. "")
* Old state ID of resource set to non-empty (e.g. existing resource)
Attempting to use CustomizeDiff with SetNewComputed() would result in the difference previously being discarded. This update ensures that previous zero values or resource existence does not influence the propagation of the computed update.
Previously:
```
--- FAIL: TestSetNewComputed (0.00s)
--- FAIL: TestSetNewComputed/NewComputed_should_always_propagate (0.00s)
resource_diff_test.go:684: Expected (*terraform.InstanceDiff)(0xc00051cea0)({
mu: (sync.Mutex) {
state: (int32) 0,
sema: (uint32) 0
},
Attributes: (map[string]*terraform.ResourceAttrDiff) (len=1) {
(string) (len=3) "foo": (*terraform.ResourceAttrDiff)(0xc0003dcec0)({
Old: (string) "",
New: (string) "",
NewComputed: (bool) true,
NewRemoved: (bool) false,
NewExtra: (interface {}) <nil>,
RequiresNew: (bool) false,
Sensitive: (bool) false,
Type: (terraform.DiffAttrType) 0
})
},
Destroy: (bool) false,
DestroyDeposed: (bool) false,
DestroyTainted: (bool) false,
Meta: (map[string]interface {}) <nil>
})
, got (*terraform.InstanceDiff)(0xc00051ce80)({
mu: (sync.Mutex) {
state: (int32) 0,
sema: (uint32) 0
},
Attributes: (map[string]*terraform.ResourceAttrDiff) {
},
Destroy: (bool) false,
DestroyDeposed: (bool) false,
DestroyTainted: (bool) false,
Meta: (map[string]interface {}) <nil>
})
--- FAIL: TestSchemaMap_Diff (0.01s)
--- FAIL: TestSchemaMap_Diff/79-NewComputed_should_always_propagate_with_CustomizeDiff (0.00s)
schema_test.go:3289: expected:
*terraform.InstanceDiff{mu:sync.Mutex{state:0, sema:0x0}, Attributes:map[string]*terraform.ResourceAttrDiff{"foo":*terraform.ResourceAttrDiff{Old:"", New:"", NewComputed:true, NewRemoved:false, NewExtra:interface {}(nil), RequiresNew:false, Sensitive:false, Type:0x0}}, Destroy:false, DestroyDeposed:false, DestroyTainted:false, Meta:map[string]interface {}(nil)}
got:
<nil>
FAIL
FAIL github.com/hashicorp/terraform/helper/schema 0.825s
```
2018-12-05 04:48:30 +01:00
|
|
|
{
|
|
|
|
Name: "NewComputed should always propagate with CustomizeDiff",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo": "",
|
|
|
|
},
|
|
|
|
ID: "pre-existing",
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
|
|
|
d.SetNewComputed("foo")
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"foo": &terraform.ResourceAttrDiff{
|
|
|
|
NewComputed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2017-05-28 18:48:15 +02:00
|
|
|
{
|
|
|
|
Name: "vetoing a diff",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"foo": "baz",
|
|
|
|
},
|
|
|
|
|
2017-05-31 19:42:41 +02:00
|
|
|
CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
|
2017-05-28 18:48:15 +02:00
|
|
|
return fmt.Errorf("diff vetoed")
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2017-12-19 21:07:37 +01:00
|
|
|
|
|
|
|
// A lot of resources currently depended on using the empty string as a
|
|
|
|
// nil/unset value.
|
2017-12-20 14:46:45 +01:00
|
|
|
// FIXME: We want this to eventually produce a diff, since there
|
|
|
|
// technically is a new value in the config.
|
2017-12-19 21:07:37 +01:00
|
|
|
{
|
|
|
|
Name: "optional, computed, empty string",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"attr": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"attr": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"attr": "",
|
2017-12-19 21:07:37 +01:00
|
|
|
},
|
|
|
|
},
|
2018-02-01 13:05:22 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
Name: "optional, computed, empty string should not crash in CustomizeDiff",
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"unrelated_set": {
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
"stream_enabled": {
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"stream_view_type": {
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: &terraform.InstanceState{
|
|
|
|
Attributes: map[string]string{
|
|
|
|
"unrelated_set.#": "0",
|
|
|
|
"stream_enabled": "true",
|
|
|
|
"stream_view_type": "KEYS_ONLY",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"stream_enabled": false,
|
|
|
|
"stream_view_type": "",
|
|
|
|
},
|
|
|
|
CustomizeDiff: func(diff *ResourceDiff, v interface{}) error {
|
|
|
|
v, ok := diff.GetOk("unrelated_set")
|
|
|
|
if ok {
|
|
|
|
return fmt.Errorf("Didn't expect unrelated_set: %#v", v)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
Diff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"stream_enabled": {
|
|
|
|
Old: "true",
|
|
|
|
New: "false",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2014-08-15 04:55:47 +02:00
|
|
|
}
|
|
|
|
|
2016-11-09 02:17:44 +01:00
|
|
|
for i, tc := range cases {
|
|
|
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
2019-07-31 06:05:09 +02:00
|
|
|
c := terraform.NewResourceConfigRaw(tc.Config)
|
2014-08-15 04:55:47 +02:00
|
|
|
|
2019-07-31 06:05:09 +02:00
|
|
|
d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, true)
|
2016-10-26 02:56:45 +02:00
|
|
|
if err != nil != tc.Err {
|
2016-11-09 02:17:44 +01:00
|
|
|
t.Fatalf("err: %s", err)
|
2016-10-26 02:56:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(tc.Diff, d) {
|
2016-11-09 02:17:44 +01:00
|
|
|
t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d)
|
2016-10-26 02:56:45 +02:00
|
|
|
}
|
|
|
|
})
|
2014-08-15 04:55:47 +02:00
|
|
|
}
|
|
|
|
}
|
2014-08-16 07:00:16 +02:00
|
|
|
|
2014-09-29 19:25:43 +02:00
|
|
|
func TestSchemaMap_Input(t *testing.T) {
|
2015-01-27 21:23:49 +01:00
|
|
|
cases := map[string]struct {
|
2014-09-29 19:25:43 +02:00
|
|
|
Schema map[string]*Schema
|
|
|
|
Config map[string]interface{}
|
|
|
|
Input map[string]string
|
|
|
|
Result map[string]interface{}
|
|
|
|
Err bool
|
|
|
|
}{
|
|
|
|
/*
|
|
|
|
* String decode
|
|
|
|
*/
|
|
|
|
|
2017-03-17 19:43:37 +01:00
|
|
|
"no input on optional field with no config": {
|
2014-09-29 20:16:19 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2017-03-17 19:43:37 +01:00
|
|
|
Input: map[string]string{},
|
|
|
|
Result: map[string]interface{}{},
|
|
|
|
Err: false,
|
2014-09-29 20:16:19 +02:00
|
|
|
},
|
|
|
|
|
2015-01-27 21:23:49 +01:00
|
|
|
"input ignored when config has a value": {
|
2014-09-29 19:25:43 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Input: map[string]string{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
2014-10-18 23:54:42 +02:00
|
|
|
Result: map[string]interface{}{},
|
2014-09-29 19:25:43 +02:00
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-10-13 02:37:52 +02:00
|
|
|
|
2015-01-27 21:23:49 +01:00
|
|
|
"input ignored when schema has a default": {
|
2014-10-13 02:37:52 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Default: "foo",
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Input: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Result: map[string]interface{}{},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-01-27 21:23:49 +01:00
|
|
|
"input ignored when default function returns a value": {
|
2014-10-13 02:37:52 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return "foo", nil
|
|
|
|
},
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Input: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Result: map[string]interface{}{},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-04-09 18:49:03 +02:00
|
|
|
"input ignored when default function returns an empty string": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Default: "",
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Input: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Result: map[string]interface{}{},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-01-27 21:23:49 +01:00
|
|
|
"input used when default function returns nil": {
|
2014-10-13 02:37:52 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
},
|
2017-03-17 19:43:37 +01:00
|
|
|
Required: true,
|
2014-10-13 02:37:52 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Input: map[string]string{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Result: map[string]interface{}{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2017-03-17 19:43:37 +01:00
|
|
|
|
|
|
|
"input not used when optional default function returns nil": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
},
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Input: map[string]string{},
|
|
|
|
Result: map[string]interface{}{},
|
|
|
|
Err: false,
|
|
|
|
},
|
2014-09-29 19:25:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for i, tc := range cases {
|
2014-09-29 20:16:19 +02:00
|
|
|
if tc.Config == nil {
|
|
|
|
tc.Config = make(map[string]interface{})
|
|
|
|
}
|
|
|
|
|
2014-09-29 19:25:43 +02:00
|
|
|
input := new(terraform.MockUIInput)
|
|
|
|
input.InputReturnMap = tc.Input
|
|
|
|
|
2019-07-31 06:05:09 +02:00
|
|
|
rc := terraform.NewResourceConfigRaw(tc.Config)
|
2014-10-18 23:54:42 +02:00
|
|
|
rc.Config = make(map[string]interface{})
|
|
|
|
|
|
|
|
actual, err := schemaMap(tc.Schema).Input(input, rc)
|
2015-10-08 14:48:04 +02:00
|
|
|
if err != nil != tc.Err {
|
2015-01-27 21:23:49 +01:00
|
|
|
t.Fatalf("#%v err: %s", i, err)
|
2014-09-29 19:25:43 +02:00
|
|
|
}
|
|
|
|
|
2014-10-18 23:54:42 +02:00
|
|
|
if !reflect.DeepEqual(tc.Result, actual.Config) {
|
2015-01-27 21:23:49 +01:00
|
|
|
t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
|
2014-09-29 19:25:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-02 17:22:11 +02:00
|
|
|
func TestSchemaMap_InputDefault(t *testing.T) {
|
|
|
|
emptyConfig := make(map[string]interface{})
|
2019-07-31 06:05:09 +02:00
|
|
|
rc := terraform.NewResourceConfigRaw(emptyConfig)
|
2015-07-02 17:22:11 +02:00
|
|
|
rc.Config = make(map[string]interface{})
|
|
|
|
|
|
|
|
input := new(terraform.MockUIInput)
|
|
|
|
input.InputFn = func(opts *terraform.InputOpts) (string, error) {
|
|
|
|
t.Fatalf("InputFn should not be called on: %#v", opts)
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
schema := map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Default: "foo",
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
actual, err := schemaMap(schema).Input(input, rc)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]interface{}{}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(expected, actual.Config) {
|
|
|
|
t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-07 18:27:04 +01:00
|
|
|
func TestSchemaMap_InputDeprecated(t *testing.T) {
|
|
|
|
emptyConfig := make(map[string]interface{})
|
2019-07-31 06:05:09 +02:00
|
|
|
rc := terraform.NewResourceConfigRaw(emptyConfig)
|
2015-12-07 18:27:04 +01:00
|
|
|
rc.Config = make(map[string]interface{})
|
|
|
|
|
|
|
|
input := new(terraform.MockUIInput)
|
|
|
|
input.InputFn = func(opts *terraform.InputOpts) (string, error) {
|
|
|
|
t.Fatalf("InputFn should not be called on: %#v", opts)
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
schema := map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Deprecated: "long gone",
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
actual, err := schemaMap(schema).Input(input, rc)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]interface{}{}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(expected, actual.Config) {
|
|
|
|
t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-17 23:50:44 +02:00
|
|
|
func TestSchemaMap_InternalValidate(t *testing.T) {
|
2015-11-20 20:41:34 +01:00
|
|
|
cases := map[string]struct {
|
2014-08-17 23:50:44 +02:00
|
|
|
In map[string]*Schema
|
|
|
|
Err bool
|
|
|
|
}{
|
2015-11-20 20:41:34 +01:00
|
|
|
"nothing": {
|
2014-08-17 23:50:44 +02:00
|
|
|
nil,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Both optional and required": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"No optional and no required": {
|
2014-08-25 01:52:51 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Missing Type": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Required but computed": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Looks good": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Computed but has default": {
|
2014-09-10 06:17:29 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Default: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Required but has default": {
|
2014-09-10 06:33:08 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Required: true,
|
|
|
|
Default: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"List element not set": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"List default": {
|
2014-09-10 06:17:29 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Default: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"List element computed": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2014-08-25 01:52:51 +02:00
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
2014-08-17 23:50:44 +02:00
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"List element with Set set": {
|
2014-08-21 06:13:18 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2014-08-25 01:52:51 +02:00
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(interface{}) int { return 0 },
|
|
|
|
Optional: true,
|
2014-08-21 06:13:18 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Set element with no Set set": {
|
2014-08-21 06:13:18 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2014-08-25 01:52:51 +02:00
|
|
|
Type: TypeSet,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Optional: true,
|
2014-08-21 06:13:18 +02:00
|
|
|
},
|
|
|
|
},
|
2015-08-18 04:26:58 +02:00
|
|
|
false,
|
2014-08-21 06:13:18 +02:00
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Required but computedWhen": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
ComputedWhen: []string{"foo"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Conflicting attributes cannot be required": {
|
2015-04-19 12:54:45 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"blacklist": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"blacklist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Attribute with conflicts cannot be required": {
|
2015-04-19 12:54:45 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Required: true,
|
|
|
|
ConflictsWith: []string{"blacklist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"ConflictsWith cannot be used w/ ComputedWhen": {
|
2015-04-19 12:54:45 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"blacklist": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
ComputedWhen: []string{"foor"},
|
|
|
|
},
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Required: true,
|
|
|
|
ConflictsWith: []string{"blacklist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Sub-resource invalid": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2014-08-25 01:52:51 +02:00
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
2014-08-17 23:50:44 +02:00
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo": new(Schema),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"Sub-resource valid": {
|
2014-08-17 23:50:44 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2014-08-25 01:52:51 +02:00
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
2014-08-17 23:50:44 +02:00
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2014-08-25 01:52:51 +02:00
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
2014-08-17 23:50:44 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
2015-06-11 14:06:30 +02:00
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
"ValidateFunc on non-primitive": {
|
2015-06-11 14:06:30 +02:00
|
|
|
map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
2015-10-14 20:44:28 +02:00
|
|
|
Type: TypeSet,
|
2015-06-11 14:06:30 +02:00
|
|
|
Required: true,
|
2015-06-24 18:29:24 +02:00
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
2015-06-11 14:06:30 +02:00
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
2017-04-23 12:25:40 +02:00
|
|
|
|
|
|
|
"computed-only field with validateFunc": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"string": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
|
|
|
es = append(es, fmt.Errorf("this is not fine"))
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"computed-only field with diffSuppressFunc": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"string": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
|
|
|
|
// Always suppress any diff
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
2017-07-17 09:37:46 +02:00
|
|
|
|
|
|
|
"invalid field name format #1": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"with space": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"invalid field name format #2": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"WithCapitals": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"invalid field name format of a Deprecated field": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"WithCapitals": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Deprecated: "Use with_underscores instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"invalid field name format of a Removed field": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"WithCapitals": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Removed: "Use with_underscores instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
2019-03-07 01:54:18 +01:00
|
|
|
|
|
|
|
"ConfigModeBlock with Elem *Resource": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
ConfigMode: SchemaConfigModeBlock,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ConfigModeBlock Computed with Elem *Resource": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
ConfigMode: SchemaConfigModeBlock,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Resource{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true, // ConfigMode of block cannot be used for computed schema
|
|
|
|
},
|
|
|
|
|
|
|
|
"ConfigModeBlock with Elem *Schema": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
ConfigMode: SchemaConfigModeBlock,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ConfigModeBlock with no Elem": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
ConfigMode: SchemaConfigModeBlock,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ConfigModeBlock inside ConfigModeAttr": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
ConfigMode: SchemaConfigModeAttr,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"sub": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
ConfigMode: SchemaConfigModeBlock,
|
|
|
|
Elem: &Resource{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true, // ConfigMode of block cannot be used in child of schema with ConfigMode of attribute
|
|
|
|
},
|
|
|
|
|
|
|
|
"ConfigModeAuto with *Resource inside ConfigModeAttr": {
|
|
|
|
map[string]*Schema{
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
ConfigMode: SchemaConfigModeAttr,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"sub": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Resource{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
true, // in *schema.Resource with ConfigMode of attribute, so must also have ConfigMode of attribute
|
|
|
|
},
|
2014-08-17 23:50:44 +02:00
|
|
|
}
|
|
|
|
|
2015-11-20 20:41:34 +01:00
|
|
|
for tn, tc := range cases {
|
2017-04-23 12:25:40 +02:00
|
|
|
t.Run(tn, func(t *testing.T) {
|
|
|
|
err := schemaMap(tc.In).InternalValidate(nil)
|
|
|
|
if err != nil != tc.Err {
|
|
|
|
if tc.Err {
|
|
|
|
t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
|
|
|
|
}
|
|
|
|
t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In)
|
2015-04-19 12:54:45 +02:00
|
|
|
}
|
2017-04-23 12:25:40 +02:00
|
|
|
})
|
2014-08-17 23:50:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-08-31 22:26:57 +02:00
|
|
|
func TestSchemaMap_DiffSuppress(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
2019-07-31 06:05:09 +02:00
|
|
|
Schema map[string]*Schema
|
|
|
|
State *terraform.InstanceState
|
|
|
|
Config map[string]interface{}
|
|
|
|
ExpectedDiff *terraform.InstanceDiff
|
|
|
|
Err bool
|
2016-08-31 22:26:57 +02:00
|
|
|
}{
|
|
|
|
"#0 - Suppress otherwise valid diff by returning true": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": {
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
|
|
|
|
// Always suppress any diff
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
ExpectedDiff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-10-25 07:23:13 +02:00
|
|
|
|
2016-08-31 22:26:57 +02:00
|
|
|
"#1 - Don't suppress diff by returning false": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": {
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
|
|
|
|
// Always suppress any diff
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
ExpectedDiff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": {
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-10-25 07:23:13 +02:00
|
|
|
|
|
|
|
"Default with suppress makes no diff": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": {
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "foo",
|
|
|
|
DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
ExpectedDiff: nil,
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Default with false suppress makes diff": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": {
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "foo",
|
|
|
|
DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
ExpectedDiff: &terraform.InstanceDiff{
|
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"availability_zone": {
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-10-02 22:59:56 +02:00
|
|
|
|
|
|
|
"Complex structure with set of computed string should mark root set as computed": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"outer": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"outer_str": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"inner": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"inner_str": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return 2
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return 1
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"outer": []interface{}{
|
2016-10-02 22:59:56 +02:00
|
|
|
map[string]interface{}{
|
|
|
|
"outer_str": "foo",
|
2019-07-31 06:05:09 +02:00
|
|
|
"inner": []interface{}{
|
2016-10-02 22:59:56 +02:00
|
|
|
map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"inner_str": hcl2shim.UnknownVariableValue,
|
2016-10-02 22:59:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2016-11-19 18:29:48 +01:00
|
|
|
ExpectedDiff: &terraform.InstanceDiff{
|
2016-10-02 22:59:56 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"outer.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"outer.~1.outer_str": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
"outer.~1.inner.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{
|
2016-11-19 18:29:48 +01:00
|
|
|
Old: "",
|
2019-07-31 06:05:09 +02:00
|
|
|
New: hcl2shim.UnknownVariableValue,
|
2016-11-19 18:29:48 +01:00
|
|
|
NewComputed: true,
|
2016-10-02 22:59:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Complex structure with complex list of computed string should mark root set as computed": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"outer": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"outer_str": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"inner": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"inner_str": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return 1
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
State: nil,
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"outer": []interface{}{
|
2016-10-02 22:59:56 +02:00
|
|
|
map[string]interface{}{
|
|
|
|
"outer_str": "foo",
|
2019-07-31 06:05:09 +02:00
|
|
|
"inner": []interface{}{
|
2016-10-02 22:59:56 +02:00
|
|
|
map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"inner_str": hcl2shim.UnknownVariableValue,
|
2016-10-02 22:59:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2016-11-19 18:29:48 +01:00
|
|
|
ExpectedDiff: &terraform.InstanceDiff{
|
2016-10-02 22:59:56 +02:00
|
|
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
|
|
|
"outer.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"outer.~1.outer_str": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "",
|
|
|
|
New: "foo",
|
|
|
|
},
|
|
|
|
"outer.~1.inner.#": &terraform.ResourceAttrDiff{
|
|
|
|
Old: "0",
|
|
|
|
New: "1",
|
|
|
|
},
|
|
|
|
"outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{
|
2016-11-19 18:29:48 +01:00
|
|
|
Old: "",
|
2019-07-31 06:05:09 +02:00
|
|
|
New: hcl2shim.UnknownVariableValue,
|
2016-11-19 18:29:48 +01:00
|
|
|
NewComputed: true,
|
2016-10-02 22:59:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2016-08-31 22:26:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for tn, tc := range cases {
|
2016-10-25 07:23:13 +02:00
|
|
|
t.Run(tn, func(t *testing.T) {
|
2019-07-31 06:05:09 +02:00
|
|
|
c := terraform.NewResourceConfigRaw(tc.Config)
|
2016-08-31 22:26:57 +02:00
|
|
|
|
2019-07-31 06:05:09 +02:00
|
|
|
d, err := schemaMap(tc.Schema).Diff(tc.State, c, nil, nil, true)
|
2016-10-25 07:23:13 +02:00
|
|
|
if err != nil != tc.Err {
|
|
|
|
t.Fatalf("#%q err: %s", tn, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(tc.ExpectedDiff, d) {
|
|
|
|
t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
|
|
|
|
}
|
|
|
|
})
|
2016-08-31 22:26:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-16 07:00:16 +02:00
|
|
|
func TestSchemaMap_Validate(t *testing.T) {
|
2015-03-05 19:28:53 +01:00
|
|
|
cases := map[string]struct {
|
2015-03-05 22:16:50 +01:00
|
|
|
Schema map[string]*Schema
|
|
|
|
Config map[string]interface{}
|
|
|
|
Err bool
|
2015-03-05 22:33:56 +01:00
|
|
|
Errors []error
|
2015-03-05 22:16:50 +01:00
|
|
|
Warnings []string
|
2014-08-16 07:00:16 +02:00
|
|
|
}{
|
2015-03-05 19:28:53 +01:00
|
|
|
"Good": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Good, because the var is not set and that error will come elsewhere": {
|
2014-10-16 23:04:45 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"size": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"size": hcl2shim.UnknownVariableValue,
|
2014-10-16 23:04:45 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Required field not set": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Invalid basic type": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"port": "I am invalid",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Invalid complex type": {
|
2014-10-20 04:56:46 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2019-07-31 06:05:09 +02:00
|
|
|
"Bad type": {
|
2014-10-16 23:04:45 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"size": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"size": "nope",
|
2014-10-16 23:04:45 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Required but has DefaultFunc": {
|
2014-09-10 06:33:08 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return "foo", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Required but has DefaultFunc return nil": {
|
2014-09-10 06:33:08 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
DefaultFunc: func() (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: nil,
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2016-12-23 02:55:23 +01:00
|
|
|
"List with promotion": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
PromoteSingle: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": "5",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"List with promotion set as list": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
PromoteSingle: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{"5"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Optional sub-resource": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-06-24 01:39:02 +02:00
|
|
|
"Sub-resource is the wrong type": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{"foo"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2019-01-04 22:44:03 +01:00
|
|
|
"Not a list nested block": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
2019-01-04 22:44:03 +01:00
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
2014-08-16 07:00:16 +02:00
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
2019-01-04 22:44:03 +01:00
|
|
|
Errors: []error{
|
|
|
|
fmt.Errorf(`ingress: should be a list`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"Not a list primitive": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"strings": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"strings": "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
|
|
|
fmt.Errorf(`strings: should be a list`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"Unknown list": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"strings": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-18 04:41:24 +02:00
|
|
|
"strings": hcl2shim.UnknownVariableValue,
|
2019-01-04 22:44:03 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
2014-08-16 07:00:16 +02:00
|
|
|
},
|
|
|
|
|
2019-07-30 22:00:43 +02:00
|
|
|
"Unknown + Deprecation": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"old_news": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Deprecated: "please use 'new_news' instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"old_news": hcl2shim.UnknownVariableValue,
|
|
|
|
},
|
|
|
|
|
|
|
|
Warnings: []string{
|
|
|
|
"\"old_news\": [DEPRECATED] please use 'new_news' instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Required sub-resource field": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{
|
|
|
|
map[string]interface{}{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Good sub-resource": {
|
2014-08-16 07:00:16 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
2014-08-16 18:18:45 +02:00
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
2014-08-16 07:00:16 +02:00
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"from": 80,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2017-05-24 10:04:25 +02:00
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2017-05-24 18:31:22 +02:00
|
|
|
"Good sub-resource, computed value": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"from": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
map[string]interface{}{
|
|
|
|
"from": hcl2shim.UnknownVariableValue,
|
|
|
|
},
|
2017-05-24 18:31:22 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2014-08-16 07:00:16 +02:00
|
|
|
Err: false,
|
|
|
|
},
|
2014-08-16 07:15:10 +02:00
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Invalid/unknown field": {
|
2014-08-16 07:15:10 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2014-08-16 18:18:45 +02:00
|
|
|
|
2015-04-22 12:52:26 +02:00
|
|
|
"Invalid/unknown field with computed value": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"foo": hcl2shim.UnknownVariableValue,
|
2015-04-22 12:52:26 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Computed field set": {
|
2014-08-16 18:18:45 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": "bar",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2014-10-18 08:23:50 +02:00
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Not a set": {
|
2014-10-18 08:23:50 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ports": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Required: true,
|
|
|
|
Elem: &Schema{Type: TypeInt},
|
|
|
|
Set: func(a interface{}) int {
|
|
|
|
return a.(int)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ports": "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2014-10-20 05:33:00 +02:00
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Maps": {
|
2014-10-20 05:33:00 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": "foo",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Good map: data surrounded by extra slice": {
|
2014-10-20 05:33:00 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Good map": {
|
2014-10-20 05:33:00 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2018-03-14 22:50:41 +01:00
|
|
|
"Map with type specified as value type": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Elem: TypeBool,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": map[string]interface{}{
|
|
|
|
"foo": "not_a_bool",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Map with type specified as nested Schema": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeBool},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": map[string]interface{}{
|
|
|
|
"foo": "not_a_bool",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Bad map: just a slice": {
|
2014-10-20 05:33:00 +02:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"user_data": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"user_data": []interface{}{
|
|
|
|
"foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2015-01-12 13:57:47 +01:00
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Good set: config has slice with single interpolated value": {
|
2015-01-12 13:57:47 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"security_groups": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
return len(v.(string))
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"security_groups": []interface{}{"${var.foo}"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Bad set: config has single interpolated value": {
|
2015-01-12 13:57:47 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"security_groups": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"security_groups": "${var.foo}",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2015-02-18 18:41:55 +01:00
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Bad, subresource should not allow unknown elements": {
|
2015-02-18 18:41:55 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"port": 80,
|
|
|
|
"other": "yes",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
"Bad, subresource should not allow invalid types": {
|
2015-02-18 18:41:55 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"ingress": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"port": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"ingress": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"port": "bad",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
2015-03-05 22:16:50 +01:00
|
|
|
|
2015-08-16 02:16:14 +02:00
|
|
|
"Bad, should not allow lists to be assigned to string attributes": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": []interface{}{"foo", "bar", "baz"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Bad, should not allow maps to be assigned to string attributes": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
2015-03-05 22:16:50 +01:00
|
|
|
"Deprecated attribute usage generates warning, but not error": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"old_news": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Deprecated: "please use 'new_news' instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"old_news": "extra extra!",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
|
|
|
|
Warnings: []string{
|
|
|
|
"\"old_news\": [DEPRECATED] please use 'new_news' instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"Deprecated generates no warnings if attr not used": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"old_news": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Deprecated: "please use 'new_news' instead",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
|
|
|
|
Warnings: nil,
|
|
|
|
},
|
|
|
|
|
2015-03-05 22:33:56 +01:00
|
|
|
"Removed attribute usage generates error": {
|
2015-03-05 22:16:50 +01:00
|
|
|
Schema: map[string]*Schema{
|
2015-03-05 22:33:56 +01:00
|
|
|
"long_gone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Removed: "no longer supported by Cloud API",
|
2015-03-05 22:16:50 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2015-03-05 22:33:56 +01:00
|
|
|
"long_gone": "still here!",
|
2015-03-05 22:16:50 +01:00
|
|
|
},
|
|
|
|
|
2015-03-05 22:33:56 +01:00
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
|
|
|
fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
|
2015-03-05 22:16:50 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-03-05 22:33:56 +01:00
|
|
|
"Removed generates no errors if attr not used": {
|
2015-03-05 22:16:50 +01:00
|
|
|
Schema: map[string]*Schema{
|
2015-03-05 22:33:56 +01:00
|
|
|
"long_gone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Removed: "no longer supported by Cloud API",
|
2015-03-05 22:16:50 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2015-04-19 12:54:45 +02:00
|
|
|
|
|
|
|
"Conflicting attributes generate error": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"blacklist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"whitelist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"whitelist": "white-val",
|
|
|
|
"blacklist": "black-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
2018-03-30 13:53:59 +02:00
|
|
|
fmt.Errorf("\"blacklist\": conflicts with whitelist"),
|
2015-04-19 12:54:45 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-01-04 22:44:03 +01:00
|
|
|
"Conflicting attributes okay when unknown 1": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"blacklist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"whitelist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"whitelist": "white-val",
|
2019-07-18 04:41:24 +02:00
|
|
|
"blacklist": hcl2shim.UnknownVariableValue,
|
2019-01-04 22:44:03 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Conflicting attributes okay when unknown 2": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"blacklist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"whitelist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-18 04:41:24 +02:00
|
|
|
"whitelist": hcl2shim.UnknownVariableValue,
|
2019-01-04 22:44:03 +01:00
|
|
|
"blacklist": "black-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Conflicting attributes generate error even if one is unknown": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"whitelist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"blacklist", "greenlist"},
|
|
|
|
},
|
|
|
|
"blacklist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"whitelist", "greenlist"},
|
|
|
|
},
|
|
|
|
"greenlist": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"whitelist", "blacklist"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2019-07-18 04:41:24 +02:00
|
|
|
"whitelist": hcl2shim.UnknownVariableValue,
|
2019-01-04 22:44:03 +01:00
|
|
|
"blacklist": "black-val",
|
|
|
|
"greenlist": "green-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
|
|
|
fmt.Errorf("\"blacklist\": conflicts with greenlist"),
|
|
|
|
fmt.Errorf("\"greenlist\": conflicts with blacklist"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-04-19 12:54:45 +02:00
|
|
|
"Required attribute & undefined conflicting optional are good": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"required_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"optional_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"required_att"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"required_att": "required-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Required conflicting attribute & defined optional generate error": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"required_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"optional_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ConflictsWith: []string{"required_att"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"required_att": "required-val",
|
|
|
|
"optional_att": "optional-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
2018-03-30 13:53:59 +02:00
|
|
|
fmt.Errorf(`"optional_att": conflicts with required_att`),
|
2015-06-08 03:49:15 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2016-11-03 06:23:11 +01:00
|
|
|
"Computed + Optional fields conflicting with each other": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ConflictsWith: []string{"bar_att"},
|
|
|
|
},
|
|
|
|
"bar_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ConflictsWith: []string{"foo_att"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"foo_att": "foo-val",
|
|
|
|
"bar_att": "bar-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
2018-03-30 13:53:59 +02:00
|
|
|
fmt.Errorf(`"foo_att": conflicts with bar_att`),
|
|
|
|
fmt.Errorf(`"bar_att": conflicts with foo_att`),
|
2016-11-03 06:23:11 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"Computed + Optional fields NOT conflicting with each other": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ConflictsWith: []string{"bar_att"},
|
|
|
|
},
|
|
|
|
"bar_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ConflictsWith: []string{"foo_att"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"foo_att": "foo-val",
|
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Computed + Optional fields that conflict with none set": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"foo_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ConflictsWith: []string{"bar_att"},
|
|
|
|
},
|
|
|
|
"bar_att": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ConflictsWith: []string{"foo_att"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
2015-06-08 03:49:15 +02:00
|
|
|
"Good with ValidateFunc": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"validate_me": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
2015-06-24 18:29:24 +02:00
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
2015-06-08 03:49:15 +02:00
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"validate_me": "valid",
|
|
|
|
},
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"Bad with ValidateFunc": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"validate_me": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
2015-06-24 18:29:24 +02:00
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
2015-06-08 03:49:15 +02:00
|
|
|
es = append(es, fmt.Errorf("something is not right here"))
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"validate_me": "invalid",
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
2015-06-11 14:06:30 +02:00
|
|
|
fmt.Errorf(`something is not right here`),
|
2015-04-19 12:54:45 +02:00
|
|
|
},
|
|
|
|
},
|
2015-06-08 15:51:04 +02:00
|
|
|
|
|
|
|
"ValidateFunc not called when type does not match": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"number": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
2015-06-24 18:29:24 +02:00
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
2015-06-08 15:51:04 +02:00
|
|
|
t.Fatalf("Should not have gotten validate call")
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"number": "NaN",
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
2015-06-24 07:10:46 +02:00
|
|
|
|
2015-06-11 14:06:30 +02:00
|
|
|
"ValidateFunc gets decoded type": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"maybe": &Schema{
|
|
|
|
Type: TypeBool,
|
|
|
|
Required: true,
|
2015-06-24 18:29:24 +02:00
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
2015-06-11 14:06:30 +02:00
|
|
|
if _, ok := v.(bool); !ok {
|
|
|
|
t.Fatalf("Expected bool, got: %#v", v)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"maybe": "true",
|
|
|
|
},
|
|
|
|
},
|
2015-06-24 07:10:46 +02:00
|
|
|
|
|
|
|
"ValidateFunc is not called with a computed value": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"validate_me": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Required: true,
|
2015-06-24 18:29:24 +02:00
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
2015-06-24 07:10:46 +02:00
|
|
|
es = append(es, fmt.Errorf("something is not right here"))
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"validate_me": hcl2shim.UnknownVariableValue,
|
2015-06-24 07:10:46 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2017-03-02 18:07:49 +01:00
|
|
|
|
2017-03-09 21:40:14 +01:00
|
|
|
"special timeouts field": {
|
2017-03-02 18:07:49 +01:00
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"availability_zone": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
2017-03-09 21:40:14 +01:00
|
|
|
TimeoutsConfigKey: "bar",
|
2017-03-02 18:07:49 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
Err: false,
|
|
|
|
},
|
2017-03-13 16:58:58 +01:00
|
|
|
|
|
|
|
"invalid bool field": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"bool_field": {
|
|
|
|
Type: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"bool_field": "abcdef",
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
"invalid integer field": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"integer_field": {
|
|
|
|
Type: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"integer_field": "abcdef",
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
"invalid float field": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"float_field": {
|
|
|
|
Type: TypeFloat,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"float_field": "abcdef",
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Invalid map values
|
|
|
|
"invalid bool map value": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"boolMap": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Elem: TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"boolMap": map[string]interface{}{
|
|
|
|
"boolField": "notbool",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
"invalid int map value": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"intMap": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Elem: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"intMap": map[string]interface{}{
|
|
|
|
"intField": "notInt",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
"invalid float map value": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"floatMap": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Elem: TypeFloat,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"floatMap": map[string]interface{}{
|
|
|
|
"floatField": "notFloat",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"map with positive validate function": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"floatInt": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Elem: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"floatInt": map[string]interface{}{
|
|
|
|
"rightAnswer": "42",
|
|
|
|
"tooMuch": "43",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Err: false,
|
|
|
|
},
|
|
|
|
"map with negative validate function": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"floatInt": &Schema{
|
|
|
|
Type: TypeMap,
|
|
|
|
Elem: TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
|
|
|
es = append(es, fmt.Errorf("this is not fine"))
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"floatInt": map[string]interface{}{
|
|
|
|
"rightAnswer": "42",
|
|
|
|
"tooMuch": "43",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Err: true,
|
|
|
|
},
|
2017-03-24 15:20:23 +01:00
|
|
|
|
|
|
|
// The Validation function should not see interpolation strings from
|
|
|
|
// non-computed values.
|
|
|
|
"set with partially computed list and map": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"outer": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"list": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
|
|
|
if strings.HasPrefix(v.(string), "${") {
|
|
|
|
es = append(es, fmt.Errorf("should not have interpolations"))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: map[string]interface{}{
|
2019-07-31 06:05:09 +02:00
|
|
|
"outer": []interface{}{
|
|
|
|
map[string]interface{}{
|
|
|
|
"list": []interface{}{"A", hcl2shim.UnknownVariableValue, "c"},
|
2017-03-24 15:20:23 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Err: false,
|
|
|
|
},
|
2019-07-27 21:11:11 +02:00
|
|
|
"unexpected nils values": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"strings": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"block": &Schema{
|
|
|
|
Type: TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Resource{
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"int": &Schema{
|
|
|
|
Type: TypeInt,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"strings": []interface{}{"1", nil},
|
|
|
|
"block": []interface{}{map[string]interface{}{
|
|
|
|
"int": nil,
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
},
|
2019-07-31 17:00:37 +02:00
|
|
|
Err: true,
|
2019-07-27 21:11:11 +02:00
|
|
|
},
|
2014-08-16 07:00:16 +02:00
|
|
|
}
|
|
|
|
|
2015-03-05 19:28:53 +01:00
|
|
|
for tn, tc := range cases {
|
2017-03-13 16:58:58 +01:00
|
|
|
t.Run(tn, func(t *testing.T) {
|
2019-07-31 06:05:09 +02:00
|
|
|
c := terraform.NewResourceConfigRaw(tc.Config)
|
2014-08-16 07:00:16 +02:00
|
|
|
|
2019-07-31 06:05:09 +02:00
|
|
|
ws, es := schemaMap(tc.Schema).Validate(c)
|
2017-03-13 16:58:58 +01:00
|
|
|
if len(es) > 0 != tc.Err {
|
|
|
|
if len(es) == 0 {
|
|
|
|
t.Errorf("%q: no errors", tn)
|
|
|
|
}
|
2014-08-16 07:00:16 +02:00
|
|
|
|
2017-03-13 16:58:58 +01:00
|
|
|
for _, e := range es {
|
|
|
|
t.Errorf("%q: err: %s", tn, e)
|
|
|
|
}
|
2014-08-16 07:00:16 +02:00
|
|
|
|
2017-03-13 16:58:58 +01:00
|
|
|
t.FailNow()
|
|
|
|
}
|
2015-03-05 22:33:56 +01:00
|
|
|
|
2017-03-13 16:58:58 +01:00
|
|
|
if !reflect.DeepEqual(ws, tc.Warnings) {
|
|
|
|
t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.Errors != nil {
|
|
|
|
sort.Sort(errorSort(es))
|
|
|
|
sort.Sort(errorSort(tc.Errors))
|
2016-11-05 00:51:26 +01:00
|
|
|
|
2017-03-13 16:58:58 +01:00
|
|
|
if !reflect.DeepEqual(es, tc.Errors) {
|
|
|
|
t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
|
|
|
|
}
|
2015-03-05 22:33:56 +01:00
|
|
|
}
|
2017-03-13 16:58:58 +01:00
|
|
|
})
|
|
|
|
|
2014-08-16 07:00:16 +02:00
|
|
|
}
|
|
|
|
}
|
2016-02-18 02:20:36 +01:00
|
|
|
|
|
|
|
func TestSchemaSet_ValidateMaxItems(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
Schema map[string]*Schema
|
|
|
|
State *terraform.InstanceState
|
|
|
|
Config map[string]interface{}
|
|
|
|
ConfigVariables map[string]string
|
|
|
|
Diff *terraform.InstanceDiff
|
|
|
|
Err bool
|
|
|
|
Errors []error
|
|
|
|
}{
|
|
|
|
"#0": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"aliases": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
MaxItems: 1,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
State: nil,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"aliases": []interface{}{"foo", "bar"},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
|
|
|
fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"#1": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"aliases": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
State: nil,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"aliases": []interface{}{"foo", "bar"},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: false,
|
|
|
|
Errors: nil,
|
|
|
|
},
|
|
|
|
"#2": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"aliases": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
MaxItems: 1,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
State: nil,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"aliases": []interface{}{"foo"},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: false,
|
|
|
|
Errors: nil,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for tn, tc := range cases {
|
2019-07-31 06:05:09 +02:00
|
|
|
c := terraform.NewResourceConfigRaw(tc.Config)
|
|
|
|
_, es := schemaMap(tc.Schema).Validate(c)
|
2016-02-18 02:20:36 +01:00
|
|
|
|
|
|
|
if len(es) > 0 != tc.Err {
|
|
|
|
if len(es) == 0 {
|
|
|
|
t.Errorf("%q: no errors", tn)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range es {
|
|
|
|
t.Errorf("%q: err: %s", tn, e)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.FailNow()
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.Errors != nil {
|
|
|
|
if !reflect.DeepEqual(es, tc.Errors) {
|
|
|
|
t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-04 19:37:23 +02:00
|
|
|
|
|
|
|
func TestSchemaSet_ValidateMinItems(t *testing.T) {
|
|
|
|
cases := map[string]struct {
|
|
|
|
Schema map[string]*Schema
|
|
|
|
State *terraform.InstanceState
|
|
|
|
Config map[string]interface{}
|
|
|
|
ConfigVariables map[string]string
|
|
|
|
Diff *terraform.InstanceDiff
|
|
|
|
Err bool
|
|
|
|
Errors []error
|
|
|
|
}{
|
|
|
|
"#0": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"aliases": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
MinItems: 2,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
State: nil,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"aliases": []interface{}{"foo", "bar"},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: false,
|
|
|
|
Errors: nil,
|
|
|
|
},
|
|
|
|
"#1": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"aliases": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
State: nil,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"aliases": []interface{}{"foo", "bar"},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: false,
|
|
|
|
Errors: nil,
|
|
|
|
},
|
|
|
|
"#2": {
|
|
|
|
Schema: map[string]*Schema{
|
|
|
|
"aliases": &Schema{
|
|
|
|
Type: TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
MinItems: 2,
|
|
|
|
Elem: &Schema{Type: TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
State: nil,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"aliases": []interface{}{"foo"},
|
|
|
|
},
|
|
|
|
Diff: nil,
|
|
|
|
Err: true,
|
|
|
|
Errors: []error{
|
|
|
|
fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for tn, tc := range cases {
|
2019-07-31 06:05:09 +02:00
|
|
|
c := terraform.NewResourceConfigRaw(tc.Config)
|
|
|
|
_, es := schemaMap(tc.Schema).Validate(c)
|
2016-10-04 19:37:23 +02:00
|
|
|
|
|
|
|
if len(es) > 0 != tc.Err {
|
|
|
|
if len(es) == 0 {
|
|
|
|
t.Errorf("%q: no errors", tn)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range es {
|
|
|
|
t.Errorf("%q: err: %s", tn, e)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.FailNow()
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.Errors != nil {
|
|
|
|
if !reflect.DeepEqual(es, tc.Errors) {
|
|
|
|
t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-05 00:51:26 +01:00
|
|
|
|
|
|
|
// errorSort implements sort.Interface to sort errors by their error message
|
|
|
|
type errorSort []error
|
|
|
|
|
|
|
|
func (e errorSort) Len() int { return len(e) }
|
|
|
|
func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
|
|
|
func (e errorSort) Less(i, j int) bool {
|
|
|
|
return e[i].Error() < e[j].Error()
|
|
|
|
}
|
2017-05-28 03:17:41 +02:00
|
|
|
|
|
|
|
func TestSchemaMapDeepCopy(t *testing.T) {
|
|
|
|
schema := map[string]*Schema{
|
|
|
|
"foo": &Schema{
|
|
|
|
Type: TypeString,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
source := schemaMap(schema)
|
|
|
|
dest := source.DeepCopy()
|
|
|
|
dest["foo"].ForceNew = true
|
|
|
|
if reflect.DeepEqual(source, dest) {
|
|
|
|
t.Fatalf("source and dest should not match")
|
|
|
|
}
|
|
|
|
}
|