trim index steps from RequiresNew paths

Only GetAttrSteps can actually trigger RequiresNew, but the flatmaps
paths will point to the indexed value that caused the change.
This commit is contained in:
James Bardin 2018-10-31 13:40:01 -04:00
parent 6383ffc2aa
commit f959b560a2
2 changed files with 174 additions and 0 deletions

View File

@ -28,6 +28,10 @@ func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) {
paths = append(paths, p) paths = append(paths, p)
} }
// now trim off any trailing paths that aren't GetAttrSteps, since only an
// attribute itself can require replacement
paths = trimPaths(paths)
// There may be redundant paths due to set elements or index attributes // There may be redundant paths due to set elements or index attributes
// Do some ugly n^2 filtering, but these are always fairly small sets. // Do some ugly n^2 filtering, but these are always fairly small sets.
for i := 0; i < len(paths)-1; i++ { for i := 0; i < len(paths)-1; i++ {
@ -44,6 +48,30 @@ func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) {
return paths, nil return paths, nil
} }
// trimPaths removes any trailing steps that aren't of type GetAttrSet, since
// only an attribute itself can require replacement
func trimPaths(paths []cty.Path) []cty.Path {
var trimmed []cty.Path
for _, path := range paths {
path = trimPath(path)
if len(path) > 0 {
trimmed = append(trimmed, path)
}
}
return trimmed
}
func trimPath(path cty.Path) cty.Path {
for len(path) > 0 {
_, isGetAttr := path[len(path)-1].(cty.GetAttrStep)
if isGetAttr {
break
}
path = path[:len(path)-1]
}
return path
}
// requiresReplacePath takes a key from a flatmap along with the cty.Type // requiresReplacePath takes a key from a flatmap along with the cty.Type
// describing the structure, and returns the cty.Path that would be used to // describing the structure, and returns the cty.Path that would be used to
// reference the nested value in the data structure. // reference the nested value in the data structure.

View File

@ -6,9 +6,18 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
) )
var (
ignoreUnexported = cmpopts.IgnoreUnexported(cty.GetAttrStep{}, cty.IndexStep{})
valueComparer = cmp.Comparer(cty.Value.RawEquals)
)
func TestPathFromFlatmap(t *testing.T) { func TestPathFromFlatmap(t *testing.T) {
tests := []struct { tests := []struct {
Flatmap string Flatmap string
@ -221,3 +230,140 @@ func TestPathFromFlatmap(t *testing.T) {
}) })
} }
} }
func TestRequiresReplace(t *testing.T) {
for _, tc := range []struct {
name string
attrs []string
expected []cty.Path
ty cty.Type
}{
{
name: "basic",
attrs: []string{
"foo",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.String,
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
},
},
{
name: "two",
attrs: []string{
"foo",
"bar",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.String,
"bar": cty.String,
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
cty.Path{cty.GetAttrStep{Name: "bar"}},
},
},
{
name: "nested object",
attrs: []string{
"foo.bar",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Object(map[string]cty.Type{
"bar": cty.String,
}),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.GetAttrStep{Name: "bar"}},
},
},
{
name: "nested objects",
attrs: []string{
"foo.bar.baz",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Object(map[string]cty.Type{
"bar": cty.Object(map[string]cty.Type{
"baz": cty.String,
}),
}),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.GetAttrStep{Name: "bar"}, cty.GetAttrStep{Name: "baz"}},
},
},
{
name: "nested map",
attrs: []string{
"foo.%",
"foo.bar",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.String),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
},
},
{
name: "nested list",
attrs: []string{
"foo.#",
"foo.1",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.String),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
},
},
{
name: "object in map",
attrs: []string{
"foo.bar.baz",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.Object(
map[string]cty.Type{
"baz": cty.String,
},
)),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.IndexStep{Key: cty.StringVal("bar")}, cty.GetAttrStep{Name: "baz"}},
},
},
{
name: "object in list",
attrs: []string{
"foo.1.baz",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.List(cty.Object(
map[string]cty.Type{
"baz": cty.String,
},
)),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.GetAttrStep{Name: "baz"}},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
rp, err := RequiresReplace(tc.attrs, tc.ty)
if err != nil {
t.Fatal(err)
}
if !cmp.Equal(tc.expected, rp, ignoreUnexported, valueComparer) {
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expected, rp)
}
})
}
}