add hcl2shim.PathFromFlatmapKey
PathFromFlatmapKey is used to convert a flatmap key to a cty.Path, and ensures it conforms to the type schema. This is used when handling Diffs, where the ResourceAttrDiffs are indexed by the flatmapped key values, and we need to convert those to addresses to apply to a cty.Value.
This commit is contained in:
parent
6c01444a8a
commit
44e993ec29
|
@ -0,0 +1,208 @@
|
|||
package hcl2shim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// PathFromFlatmapKey takes a key from a flatmap along with the cty.Type
|
||||
// describing the structure, and returns the cty.Path that would be used to
|
||||
// reference the nested value in the data structure.
|
||||
func PathFromFlatmapKey(k string, ty cty.Type) (cty.Path, error) {
|
||||
if k == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if !ty.IsObjectType() {
|
||||
panic(fmt.Sprintf("PathFromFlatmapKey called on %#v", ty))
|
||||
}
|
||||
|
||||
path, err := pathFromFlatmapKeyObject(k, ty.AttributeTypes())
|
||||
if err != nil {
|
||||
return path, fmt.Errorf("[%s] %s", k, err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func pathSplit(p string) (string, string) {
|
||||
parts := strings.SplitN(p, ".", 2)
|
||||
head := parts[0]
|
||||
rest := ""
|
||||
if len(parts) > 1 {
|
||||
rest = parts[1]
|
||||
}
|
||||
return head, rest
|
||||
}
|
||||
|
||||
func pathFromFlatmapKeyObject(key string, atys map[string]cty.Type) (cty.Path, error) {
|
||||
k, rest := pathSplit(key)
|
||||
|
||||
path := cty.Path{cty.GetAttrStep{Name: k}}
|
||||
|
||||
ty, ok := atys[k]
|
||||
if !ok {
|
||||
return path, fmt.Errorf("attribute %q not found", key)
|
||||
}
|
||||
|
||||
if rest == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
p, err := pathFromFlatmapKeyValue(rest, ty)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return append(path, p...), nil
|
||||
}
|
||||
|
||||
func pathFromFlatmapKeyValue(key string, ty cty.Type) (cty.Path, error) {
|
||||
var path cty.Path
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case ty.IsPrimitiveType():
|
||||
err = fmt.Errorf("invalid step %q with type %#v", key, ty)
|
||||
case ty.IsObjectType():
|
||||
path, err = pathFromFlatmapKeyObject(key, ty.AttributeTypes())
|
||||
case ty.IsTupleType():
|
||||
path, err = pathFromFlatmapKeyTuple(key, ty.TupleElementTypes())
|
||||
case ty.IsMapType():
|
||||
path, err = pathFromFlatmapKeyMap(key, ty)
|
||||
case ty.IsListType():
|
||||
path, err = pathFromFlatmapKeyList(key, ty)
|
||||
case ty.IsSetType():
|
||||
path, err = pathFromFlatmapKeySet(key, ty)
|
||||
default:
|
||||
err = fmt.Errorf("unrecognized type: %s", ty.FriendlyName())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func pathFromFlatmapKeyTuple(key string, etys []cty.Type) (cty.Path, error) {
|
||||
var path cty.Path
|
||||
var err error
|
||||
|
||||
k, rest := pathSplit(key)
|
||||
|
||||
// we don't need to convert the index keys to paths
|
||||
if k == "#" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
idx, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
|
||||
|
||||
if idx >= len(etys) {
|
||||
return path, fmt.Errorf("index %s out of range in %#v", key, etys)
|
||||
}
|
||||
|
||||
if rest == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
ty := etys[idx]
|
||||
|
||||
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return append(path, p...), nil
|
||||
}
|
||||
|
||||
func pathFromFlatmapKeyMap(key string, ty cty.Type) (cty.Path, error) {
|
||||
var path cty.Path
|
||||
var err error
|
||||
|
||||
k, rest := pathSplit(key)
|
||||
|
||||
// we don't need to convert the index keys to paths
|
||||
if k == "%" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
path = cty.Path{cty.IndexStep{Key: cty.StringVal(k)}}
|
||||
|
||||
if rest == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return append(path, p...), nil
|
||||
}
|
||||
|
||||
func pathFromFlatmapKeyList(key string, ty cty.Type) (cty.Path, error) {
|
||||
var path cty.Path
|
||||
var err error
|
||||
|
||||
k, rest := pathSplit(key)
|
||||
|
||||
// we don't need to convert the index keys to paths
|
||||
if key == "#" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
idx, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
|
||||
|
||||
if rest == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return append(path, p...), nil
|
||||
}
|
||||
|
||||
func pathFromFlatmapKeySet(key string, ty cty.Type) (cty.Path, error) {
|
||||
var path cty.Path
|
||||
var err error
|
||||
|
||||
k, rest := pathSplit(key)
|
||||
|
||||
// we don't need to convert the index keys to paths
|
||||
if k == "#" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
idx, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
|
||||
|
||||
if rest == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return append(path, p...), nil
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
package hcl2shim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestPathFromFlatmap(t *testing.T) {
|
||||
tests := []struct {
|
||||
Flatmap string
|
||||
Type cty.Type
|
||||
Want cty.Path
|
||||
WantErr string
|
||||
}{
|
||||
{
|
||||
Flatmap: "",
|
||||
Type: cty.EmptyObject,
|
||||
Want: nil,
|
||||
},
|
||||
{
|
||||
Flatmap: "attr",
|
||||
Type: cty.EmptyObject,
|
||||
Want: nil,
|
||||
WantErr: `attribute "attr" not found`,
|
||||
},
|
||||
{
|
||||
Flatmap: "foo",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.String,
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.#",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.List(cty.String),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.1",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.List(cty.String),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.NumberIntVal(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.1",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.Tuple([]cty.Type{
|
||||
cty.String,
|
||||
cty.Bool,
|
||||
}),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.NumberIntVal(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.24534534",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.Set(cty.String),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.NumberIntVal(24534534)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.%",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.Map(cty.String),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.baz",
|
||||
//FlatMap: "foo.bar.baz",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.Map(cty.Bool),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.StringVal("baz")},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.bar.baz",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.Map(
|
||||
cty.Map(cty.Bool),
|
||||
),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.StringVal("bar")},
|
||||
cty.IndexStep{Key: cty.StringVal("baz")},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.0.bar",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.List(cty.Object(map[string]cty.Type{
|
||||
"bar": cty.String,
|
||||
"baz": cty.Bool,
|
||||
})),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.NumberIntVal(0)},
|
||||
cty.GetAttrStep{Name: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Flatmap: "foo.34534534.baz",
|
||||
Type: cty.Object(map[string]cty.Type{
|
||||
"foo": cty.Set(cty.Object(map[string]cty.Type{
|
||||
"bar": cty.String,
|
||||
"baz": cty.Bool,
|
||||
})),
|
||||
}),
|
||||
Want: cty.Path{
|
||||
cty.GetAttrStep{Name: "foo"},
|
||||
cty.IndexStep{Key: cty.NumberIntVal(34534534)},
|
||||
cty.GetAttrStep{Name: "baz"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%s as %#v", test.Flatmap, test.Type), func(t *testing.T) {
|
||||
got, err := PathFromFlatmapKey(test.Flatmap, test.Type)
|
||||
|
||||
if test.WantErr != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("succeeded; want error: %s", test.WantErr)
|
||||
}
|
||||
if got, want := err.Error(), test.WantErr; !strings.Contains(got, want) {
|
||||
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for _, problem := range deep.Equal(got, test.Want) {
|
||||
t.Error(problem)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue