terraform: ResourceConfig reimplementation for TypeUnknown
The primary change here is to expect that Config contains computed values. This introduces `unknownCheckWalker` that does a really basic reflectwalk to look for computed values and use that for IsComputed. We had a weird mixture before checking whether c.Config was simply missing values to determine where to look. Now we rely on IsComputed heavily.
This commit is contained in:
parent
59bd1a22f4
commit
9b5b122f14
|
@ -24,9 +24,7 @@ func TestEvalValidateResource_managedResource(t *testing.T) {
|
|||
}
|
||||
|
||||
p := ResourceProvider(mp)
|
||||
rc := &ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
rc := testResourceConfig(t, map[string]interface{}{"foo": "bar"})
|
||||
node := &EvalValidateResource{
|
||||
Provider: &p,
|
||||
Config: &rc,
|
||||
|
@ -61,9 +59,7 @@ func TestEvalValidateResource_dataSource(t *testing.T) {
|
|||
}
|
||||
|
||||
p := ResourceProvider(mp)
|
||||
rc := &ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
rc := testResourceConfig(t, map[string]interface{}{"foo": "bar"})
|
||||
node := &EvalValidateResource{
|
||||
Provider: &p,
|
||||
Config: &rc,
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
// ResourceProvisionerConfig is used to pair a provisioner
|
||||
|
@ -186,16 +187,17 @@ func (c *ResourceConfig) CheckSet(keys []string) []error {
|
|||
// Get looks up a configuration value by key and returns the value.
|
||||
//
|
||||
// The second return value is true if the get was successful. Get will
|
||||
// not succeed if the value is being computed.
|
||||
// return the raw value if the key is computed, so you should pair this
|
||||
// with IsComputed.
|
||||
func (c *ResourceConfig) Get(k string) (interface{}, bool) {
|
||||
// First try to get it from c.Config since that has interpolated values
|
||||
result, ok := c.get(k, c.Config)
|
||||
if ok {
|
||||
return result, ok
|
||||
// We aim to get a value from the configuration. If it is computed,
|
||||
// then we return the pure raw value.
|
||||
source := c.Config
|
||||
if c.IsComputed(k) {
|
||||
source = c.Raw
|
||||
}
|
||||
|
||||
// Otherwise, just get it from the raw config
|
||||
return c.get(k, c.Raw)
|
||||
return c.get(k, source)
|
||||
}
|
||||
|
||||
// GetRaw looks up a configuration value by key and returns the value,
|
||||
|
@ -209,22 +211,25 @@ func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
|
|||
|
||||
// IsComputed returns whether the given key is computed or not.
|
||||
func (c *ResourceConfig) IsComputed(k string) bool {
|
||||
// First, check for pure computed key equality since that is fast
|
||||
for _, ck := range c.ComputedKeys {
|
||||
if ck == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// The next thing we do is check the config if we get a computed
|
||||
// value out of it.
|
||||
v, ok := c.get(k, c.Config)
|
||||
_, okRaw := c.get(k, c.Raw)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Both tests probably aren't needed anymore since we don't remove
|
||||
// values any longer. The latter is probably good enough since we
|
||||
// thread through that value now.
|
||||
return (!ok && okRaw) || v == config.UnknownVariableValue
|
||||
// If value is nil, then it isn't computed
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test if the value contains an unknown value
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(v, &w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return w.Unknown
|
||||
}
|
||||
|
||||
// IsSet checks if the key in the configuration is set. A key is set if
|
||||
|
@ -238,10 +243,8 @@ func (c *ResourceConfig) IsSet(k string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for _, ck := range c.ComputedKeys {
|
||||
if ck == k {
|
||||
return true
|
||||
}
|
||||
if c.IsComputed(k) {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := c.Get(k); ok {
|
||||
|
@ -261,7 +264,6 @@ func (c *ResourceConfig) get(
|
|||
var current interface{} = raw
|
||||
var previous interface{} = nil
|
||||
for i, part := range parts {
|
||||
println(fmt.Sprintf("%#v: %#v %T", part, current, current))
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -289,15 +291,15 @@ func (c *ResourceConfig) get(
|
|||
case reflect.Slice:
|
||||
previous = current
|
||||
|
||||
// If any value in a list is computed, this whole thing
|
||||
// is computed and we can't read any part of it.
|
||||
for i := 0; i < cv.Len(); i++ {
|
||||
if v := cv.Index(i).Interface(); v == unknownValue() {
|
||||
return v, false
|
||||
}
|
||||
}
|
||||
|
||||
if part == "#" {
|
||||
// If any value in a list is computed, this whole thing
|
||||
// is computed and we can't read any part of it.
|
||||
for i := 0; i < cv.Len(); i++ {
|
||||
if v := cv.Index(i).Interface(); v == unknownValue() {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
current = cv.Len()
|
||||
} else {
|
||||
i, err := strconv.ParseInt(part, 0, 0)
|
||||
|
@ -310,11 +312,6 @@ func (c *ResourceConfig) get(
|
|||
current = cv.Index(int(i)).Interface()
|
||||
}
|
||||
case reflect.String:
|
||||
// If we get the unknown value, return that
|
||||
if current == unknownValue() {
|
||||
return current, false
|
||||
}
|
||||
|
||||
// This happens when map keys contain "." and have a common
|
||||
// prefix so were split as path components above.
|
||||
actualKey := strings.Join(parts[i-1:], ".")
|
||||
|
@ -328,23 +325,6 @@ func (c *ResourceConfig) get(
|
|||
}
|
||||
}
|
||||
|
||||
switch cv := reflect.ValueOf(current); cv.Kind() {
|
||||
case reflect.Slice:
|
||||
// If any value in a list is computed, this whole thing
|
||||
// is computed and we can't read any part of it.
|
||||
for i := 0; i < cv.Len(); i++ {
|
||||
if v := cv.Index(i).Interface(); v == unknownValue() {
|
||||
return v, false
|
||||
}
|
||||
}
|
||||
default:
|
||||
// If the value is just the unknown value, then we don't
|
||||
// know anything beyond here.
|
||||
if current == unknownValue() {
|
||||
return current, false
|
||||
}
|
||||
}
|
||||
|
||||
return current, true
|
||||
}
|
||||
|
||||
|
@ -364,3 +344,16 @@ func (c *ResourceConfig) interpolateForce() {
|
|||
c.Raw = c.raw.RawMap()
|
||||
c.Config = c.raw.Config()
|
||||
}
|
||||
|
||||
// unknownCheckWalker
|
||||
type unknownCheckWalker struct {
|
||||
Unknown bool
|
||||
}
|
||||
|
||||
func (w *unknownCheckWalker) Primitive(v reflect.Value) error {
|
||||
if v.Interface() == unknownValue() {
|
||||
w.Unknown = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResourceConfig_CheckSet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Raw map[string]interface{}
|
||||
Computed []string
|
||||
Input []string
|
||||
Errs bool
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
[]string{"foo"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
[]string{"foo", "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
[]string{"bar"},
|
||||
[]string{"foo", "bar"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := &ResourceConfig{
|
||||
ComputedKeys: tc.Computed,
|
||||
Raw: tc.Raw,
|
||||
}
|
||||
|
||||
errs := rc.CheckSet(tc.Input)
|
||||
if tc.Errs != (len(errs) > 0) {
|
||||
t.Fatalf("bad: %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfig_Get(t *testing.T) {
|
||||
cases := []struct {
|
||||
Raw map[string]interface{}
|
||||
Computed []string
|
||||
Input string
|
||||
Output interface{}
|
||||
OutputOk bool
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
"foo",
|
||||
"bar",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
"foo",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"foo.bar",
|
||||
"baz",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": []string{
|
||||
"one",
|
||||
"two",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"foo.1",
|
||||
"two",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := &ResourceConfig{
|
||||
ComputedKeys: tc.Computed,
|
||||
Raw: tc.Raw,
|
||||
}
|
||||
|
||||
actual, ok := rc.Get(tc.Input)
|
||||
if tc.OutputOk != ok {
|
||||
t.Fatalf("bad ok: %d", i)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Output, actual) {
|
||||
t.Fatalf("bad %d: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfig_IsSet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Raw map[string]interface{}
|
||||
Computed []string
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
nil,
|
||||
"foo",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{},
|
||||
[]string{"foo"},
|
||||
"foo",
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"foo.bar",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := &ResourceConfig{
|
||||
ComputedKeys: tc.Computed,
|
||||
Raw: tc.Raw,
|
||||
}
|
||||
|
||||
actual := rc.IsSet(tc.Input)
|
||||
if actual != tc.Output {
|
||||
t.Fatalf("fail case: %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfig_IsSet_nil(t *testing.T) {
|
||||
var rc *ResourceConfig
|
||||
|
||||
if rc.IsSet("foo") {
|
||||
t.Fatal("bad")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProviderFactoryFixed(t *testing.T) {
|
||||
p := new(MockResourceProvider)
|
||||
var f ResourceProviderFactory = ResourceProviderFactoryFixed(p)
|
||||
actual, err := f()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if actual != p {
|
||||
t.Fatal("should be identical")
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/hashicorp/hil"
|
||||
"github.com/hashicorp/hil/ast"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
func TestInstanceInfo(t *testing.T) {
|
||||
|
@ -58,6 +59,14 @@ func TestResourceConfigGet(t *testing.T) {
|
|||
Value: nil,
|
||||
},
|
||||
|
||||
{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
|
||||
{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
|
@ -217,8 +226,6 @@ func TestResourceConfigGet(t *testing.T) {
|
|||
|
||||
// Test getting a key
|
||||
t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
|
||||
t.Logf("Config: %#v", rc)
|
||||
|
||||
v, _ := rc.Get(tc.Key)
|
||||
if !reflect.DeepEqual(v, tc.Value) {
|
||||
t.Fatalf("%d bad: %#v", i, v)
|
||||
|
@ -297,20 +304,22 @@ func TestResourceConfigIsComputed(t *testing.T) {
|
|||
Result: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "set count with computed elements",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": []string{
|
||||
"a",
|
||||
unknownValue(),
|
||||
/*
|
||||
{
|
||||
Name: "set count with computed elements",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": []string{
|
||||
"a",
|
||||
unknownValue(),
|
||||
},
|
||||
},
|
||||
Key: "foo.#",
|
||||
Result: true,
|
||||
},
|
||||
Key: "foo.#",
|
||||
Result: true,
|
||||
},
|
||||
*/
|
||||
|
||||
{
|
||||
Name: "set count with computed elements",
|
||||
|
@ -385,6 +394,100 @@ func TestResourceConfigIsComputed(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigCheckSet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Config map[string]interface{}
|
||||
Vars map[string]interface{}
|
||||
Input []string
|
||||
Errs bool
|
||||
}{
|
||||
{
|
||||
Name: "computed basic",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": unknownValue(),
|
||||
},
|
||||
Input: []string{"foo"},
|
||||
Errs: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "basic",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Vars: nil,
|
||||
Input: []string{"foo"},
|
||||
Errs: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "basic with not set",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Vars: nil,
|
||||
Input: []string{"foo", "bar"},
|
||||
Errs: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "basic with one computed",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]interface{}{
|
||||
"foo": unknownValue(),
|
||||
},
|
||||
Input: []string{"foo", "bar"},
|
||||
Errs: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var rawC *config.RawConfig
|
||||
if tc.Config != nil {
|
||||
var err error
|
||||
rawC, err = config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.Vars != nil {
|
||||
vs := make(map[string]ast.Variable)
|
||||
for k, v := range tc.Vars {
|
||||
hilVar, err := hil.InterfaceToVariable(v)
|
||||
if err != nil {
|
||||
t.Fatalf("%#v to var: %s", v, err)
|
||||
}
|
||||
|
||||
vs["var."+k] = hilVar
|
||||
}
|
||||
|
||||
if err := rawC.Interpolate(vs); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
rc := NewResourceConfig(rawC)
|
||||
rc.interpolateForce()
|
||||
|
||||
t.Logf("Config: %#v", rc)
|
||||
|
||||
errs := rc.CheckSet(tc.Input)
|
||||
if tc.Errs != (len(errs) > 0) {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigDeepCopy_nil(t *testing.T) {
|
||||
var nilRc *ResourceConfig
|
||||
actual := nilRc.DeepCopy()
|
||||
|
@ -428,6 +531,54 @@ func TestResourceConfigEqual_computedKeyOrder(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnknownCheckWalker(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Input interface{}
|
||||
Result bool
|
||||
}{
|
||||
{
|
||||
"primitive",
|
||||
42,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"primitive computed",
|
||||
unknownValue(),
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"list",
|
||||
[]interface{}{"foo", unknownValue()},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"nested list",
|
||||
[]interface{}{
|
||||
"foo",
|
||||
[]interface{}{unknownValue()},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(tc.Input, &w); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if w.Unknown != tc.Result {
|
||||
t.Fatalf("bad: %v", w.Unknown)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceConfig(
|
||||
t *testing.T, c map[string]interface{}) *ResourceConfig {
|
||||
raw, err := config.NewRawConfig(c)
|
||||
|
|
Loading…
Reference in New Issue