Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tftypes: Allow DynamicPseudoType with known values #136

Merged
merged 3 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/136.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
tftypes: Fixed regression with DynamicPseudoType handling since v0.4.0, allowing usage of known values again and preventing msgpack decoding errors in Terraform CLI
```
3 changes: 3 additions & 0 deletions tftypes/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ func (val1 Value) Diff(val2 Value) ([]ValueDiff, error) {
// if we have the same keys, we can just let recursion
// from the walk check the sub-values match
return true, nil
case value1.Type().Is(DynamicPseudoType):
// Let recursion from the walk check the sub-values match
return true, nil
}
return false, fmt.Errorf("unexpected type %v in Diff at %s", value1.Type(), path)
})
Expand Down
4 changes: 1 addition & 3 deletions tftypes/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ func valueFromList(typ Type, in interface{}) (Value, error) {
case []Value:
var valType Type
for pos, v := range value {
if v.Type().Is(DynamicPseudoType) && v.IsKnown() {
return Value{}, NewAttributePath().WithElementKeyInt(pos).NewErrorf("invalid value %s for %s", v, v.Type())
} else if !v.Type().Is(DynamicPseudoType) && !v.Type().UsableAs(typ) {
if !v.Type().UsableAs(typ) {
return Value{}, NewAttributePath().WithElementKeyInt(pos).NewErrorf("can't use %s as %s", v.Type(), typ)
}
if valType == nil {
Expand Down
4 changes: 1 addition & 3 deletions tftypes/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@ func valueFromMap(typ Type, in interface{}) (Value, error) {
var elType Type
for _, k := range keys {
v := value[k]
if v.Type().Is(DynamicPseudoType) && v.IsKnown() {
return Value{}, NewAttributePath().WithElementKeyString(k).NewErrorf("invalid value %s for %s", v, v.Type())
} else if !v.Type().Is(DynamicPseudoType) && !v.Type().UsableAs(typ) {
if !v.Type().UsableAs(typ) {
return Value{}, NewAttributePath().WithElementKeyString(k).NewErrorf("can't use %s as %s", v.Type(), typ)
}
if elType == nil {
Expand Down
4 changes: 1 addition & 3 deletions tftypes/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,7 @@ func valueFromObject(types map[string]Type, optionalAttrs map[string]struct{}, i
if v.Type() == nil {
return Value{}, NewAttributePath().WithAttributeName(k).NewErrorf("missing value type")
}
if v.Type().Is(DynamicPseudoType) && v.IsKnown() && !v.IsNull() {
return Value{}, NewAttributePath().WithAttributeName(k).NewErrorf("invalid value %s for %s", v, v.Type())
} else if !v.Type().Is(DynamicPseudoType) && !v.Type().UsableAs(typ) {
if !v.Type().UsableAs(typ) {
return Value{}, NewAttributePath().WithAttributeName(k).NewErrorf("can't use %s as %s", v.Type(), typ)
}
}
Expand Down
53 changes: 52 additions & 1 deletion tftypes/primitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,16 @@ func (p primitive) supportedGoTypes() []string {
case Bool.name:
return []string{"bool", "*bool"}
case DynamicPseudoType.name:
return []string{"nil", "UnknownValue"}
// List/Set is covered by Tuple, Map is covered by Object
possibleTypes := []Type{
String, Bool, Number,
Tuple{}, Object{},
}
results := []string{}
for _, t := range possibleTypes {
results = append(results, t.supportedGoTypes()...)
}
return results
}
panic(fmt.Sprintf("unknown primitive type %q", p.name))
}
Expand Down Expand Up @@ -346,3 +355,45 @@ func valueFromNumber(in interface{}) (Value, error) {
return Value{}, fmt.Errorf("tftypes.NewValue can't use %T as a tftypes.Number; expected types are: %s", in, formattedSupportedGoTypes(Number))
}
}

func valueFromDynamicPseudoType(val interface{}) (Value, error) {
switch val := val.(type) {
case string, *string:
v, err := valueFromString(val)
if err != nil {
return Value{}, err
}
v.typ = DynamicPseudoType
return v, nil
case *big.Float, float64, *float64, int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64, uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64:
v, err := valueFromNumber(val)
if err != nil {
return Value{}, err
}
v.typ = DynamicPseudoType
return v, nil
case bool, *bool:
v, err := valueFromBool(val)
if err != nil {
return Value{}, err
}
v.typ = DynamicPseudoType
return v, nil
case map[string]Value:
v, err := valueFromObject(nil, nil, val)
if err != nil {
return Value{}, err
}
v.typ = DynamicPseudoType
return v, nil
case []Value:
v, err := valueFromTuple(nil, val)
if err != nil {
return Value{}, err
}
v.typ = DynamicPseudoType
return v, nil
default:
return Value{}, fmt.Errorf("tftypes.NewValue can't use %T as a tftypes.DynamicPseudoType; expected types are: %s", val, formattedSupportedGoTypes(DynamicPseudoType))
}
}
4 changes: 1 addition & 3 deletions tftypes/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ func valueFromSet(typ Type, in interface{}) (Value, error) {
case []Value:
var elType Type
for _, v := range value {
if v.Type().Is(DynamicPseudoType) && v.IsKnown() {
return Value{}, NewAttributePath().WithElementKeyValue(v).NewErrorf("invalid value %s for %s", v, v.Type())
} else if !v.Type().Is(DynamicPseudoType) && !v.Type().UsableAs(typ) {
if !v.Type().UsableAs(typ) {
return Value{}, NewAttributePath().WithElementKeyValue(v).NewErrorf("can't use %s as %s", v.Type(), typ)
}
if elType == nil {
Expand Down
4 changes: 1 addition & 3 deletions tftypes/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ func valueFromTuple(types []Type, in interface{}) (Value, error) {
}
for pos, v := range value {
typ := types[pos]
if v.Type().Is(DynamicPseudoType) && v.IsKnown() {
return Value{}, NewAttributePath().WithElementKeyInt(pos).NewErrorf("invalid value %s for %s", v, v.Type())
} else if !v.Type().Is(DynamicPseudoType) && !v.Type().UsableAs(typ) {
if !v.Type().UsableAs(typ) {
return Value{}, NewAttributePath().WithElementKeyInt(pos).NewErrorf("can't use %s as %s", v.Type(), typ)
}
}
Expand Down
11 changes: 6 additions & 5 deletions tftypes/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package tftypes

import (
"bytes"
"errors"
"fmt"
"math/big"
"sort"
Expand Down Expand Up @@ -296,10 +295,6 @@ func newValue(t Type, val interface{}) (Value, error) {
}, nil
}

if t.Is(DynamicPseudoType) {
return Value{}, errors.New("cannot have DynamicPseudoType with known value, DynamicPseudoType can only contain null or unknown values")
}

if creator, ok := val.(ValueCreator); ok {
var err error
val, err = creator.ToTerraform5Value()
Expand Down Expand Up @@ -357,6 +352,12 @@ func newValue(t Type, val interface{}) (Value, error) {
return Value{}, err
}
return v, nil
case t.Is(DynamicPseudoType):
v, err := valueFromDynamicPseudoType(val)
if err != nil {
return Value{}, err
}
return v, nil
default:
return Value{}, fmt.Errorf("unknown type %s passed to tftypes.NewValue", t)
}
Expand Down
90 changes: 87 additions & 3 deletions tftypes/value_dpt_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tftypes

import (
"math/big"
"regexp"
"testing"
)
Expand All @@ -14,10 +15,93 @@ func Test_newValue_dpt(t *testing.T) {
expected Value
}
tests := map[string]testCase{
"known": {
"*big.Float": {
typ: DynamicPseudoType,
val: "hello",
err: regexp.MustCompile(`cannot have DynamicPseudoType with known value, DynamicPseudoType can only contain null or unknown values`),
val: big.NewFloat(123),
expected: Value{
typ: DynamicPseudoType,
value: Value{
typ: Number,
value: big.NewFloat(123),
},
},
},
"bool": {
typ: DynamicPseudoType,
val: true,
expected: Value{
typ: DynamicPseudoType,
value: true,
},
},
"float64": {
typ: DynamicPseudoType,
val: float64(123),
expected: Value{
typ: DynamicPseudoType,
value: Value{
typ: Number,
value: big.NewFloat(123),
},
},
},
"int": {
typ: DynamicPseudoType,
val: 123,
expected: Value{
typ: DynamicPseudoType,
value: Value{
typ: Number,
value: big.NewFloat(123),
},
},
},
"int64": {
typ: DynamicPseudoType,
val: int64(123),
expected: Value{
typ: DynamicPseudoType,
value: Value{
typ: Number,
value: big.NewFloat(123),
},
},
},
"object": {
typ: DynamicPseudoType,
val: map[string]Value{
"testkey": NewValue(String, "testvalue"),
},
expected: Value{
typ: DynamicPseudoType,
value: map[string]Value{
"testkey": {
typ: String,
value: "testvalue",
},
},
},
},
"string": {
typ: DynamicPseudoType,
val: "test",
expected: Value{
typ: DynamicPseudoType,
value: "test",
},
},
"tuple": {
typ: DynamicPseudoType,
val: []Value{NewValue(String, "test")},
expected: Value{
typ: DynamicPseudoType,
value: []Value{
{
typ: String,
value: "test",
},
},
},
},
"null": {
typ: DynamicPseudoType,
Expand Down
22 changes: 3 additions & 19 deletions tftypes/value_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,20 +355,8 @@ func jsonUnmarshalMap(buf []byte, attrType Type, p *AttributePath) (Value, error
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('}'), tok)
}

elTyp := attrType
if attrType.Is(DynamicPseudoType) {
var elements []Value
for _, val := range vals {
elements = append(elements, val)
}
elTyp, err = TypeFromElements(elements)
if err != nil {
return Value{}, p.NewErrorf("invalid elements for map: %w", err)
}
}

return NewValue(Map{
ElementType: elTyp,
ElementType: attrType,
}, vals), nil
}

Expand All @@ -393,7 +381,6 @@ func jsonUnmarshalTuple(buf []byte, elementTypes []Type, p *AttributePath) (Valu
// while generally in Go it's undesirable to treat empty and nil slices
// separately, in this case we're surfacing a non-Go-in-origin
// distinction, so we'll allow it.
types := []Type{}
vals := []Value{}

var idx int
Expand All @@ -415,7 +402,6 @@ func jsonUnmarshalTuple(buf []byte, elementTypes []Type, p *AttributePath) (Valu
if err != nil {
return Value{}, err
}
types = append(types, val.Type())
vals = append(vals, val)
}

Expand All @@ -432,7 +418,7 @@ func jsonUnmarshalTuple(buf []byte, elementTypes []Type, p *AttributePath) (Valu
}

return NewValue(Tuple{
ElementTypes: types,
ElementTypes: elementTypes,
}, vals), nil
}

Expand All @@ -447,7 +433,6 @@ func jsonUnmarshalObject(buf []byte, attrTypes map[string]Type, p *AttributePath
return Value{}, p.NewErrorf("invalid JSON, expected %q, got %q", json.Delim('{'), tok)
}

types := map[string]Type{}
vals := map[string]Value{}
for dec.More() {
innerPath := p.WithElementKeyValue(NewValue(String, UnknownValue))
Expand All @@ -474,7 +459,6 @@ func jsonUnmarshalObject(buf []byte, attrTypes map[string]Type, p *AttributePath
if err != nil {
return Value{}, err
}
types[key] = val.Type()
vals[key] = val
}

Expand All @@ -494,6 +478,6 @@ func jsonUnmarshalObject(buf []byte, attrTypes map[string]Type, p *AttributePath
}

return NewValue(Object{
AttributeTypes: types,
AttributeTypes: attrTypes,
}, vals), nil
}
6 changes: 3 additions & 3 deletions tftypes/value_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func TestValueFromJSON(t *testing.T) {
},
"tuple-of-dynamic-bools": {
value: NewValue(Tuple{
ElementTypes: []Type{Bool, Bool},
ElementTypes: []Type{DynamicPseudoType, DynamicPseudoType},
}, []Value{
NewValue(Bool, true),
NewValue(Bool, false),
Expand All @@ -324,7 +324,7 @@ func TestValueFromJSON(t *testing.T) {
},
"map-of-dynamic-bools": {
value: NewValue(Map{
ElementType: Bool,
ElementType: DynamicPseudoType,
}, map[string]Value{
"true": NewValue(Bool, true),
"false": NewValue(Bool, false),
Expand All @@ -338,7 +338,7 @@ func TestValueFromJSON(t *testing.T) {
value: NewValue(Object{
AttributeTypes: map[string]Type{
"static": Bool,
"dynamic": Bool,
"dynamic": DynamicPseudoType,
},
}, map[string]Value{
"static": NewValue(Bool, true),
Expand Down