Merge pull request #20158 from hashicorp/jbardin/list-block

Improve list block handling in diff application
This commit is contained in:
James Bardin 2019-01-30 15:31:05 -05:00 committed by GitHub
commit 8a3bf9a592
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 29 deletions

View File

@ -15,6 +15,27 @@ func TestResourceList_changed(t *testing.T) {
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: strings.TrimSpace(` Config: strings.TrimSpace(`
resource "test_resource_list" "foo" {
list_block {
string = "a"
int = 1
}
}
`),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"test_resource_list.foo", "list_block.#", "1",
),
resource.TestCheckResourceAttr(
"test_resource_list.foo", "list_block.0.string", "a",
),
resource.TestCheckResourceAttr(
"test_resource_list.foo", "list_block.0.int", "1",
),
),
},
resource.TestStep{
Config: strings.TrimSpace(`
resource "test_resource_list" "foo" { resource "test_resource_list" "foo" {
list_block { list_block {
string = "a" string = "a"

View File

@ -59,16 +59,6 @@ resource "test_resource_nested_set" "foo" {
// the empty type_list must be passed to the provider with 1 nil element // the empty type_list must be passed to the provider with 1 nil element
func TestResourceNestedSet_emptyBlock(t *testing.T) { func TestResourceNestedSet_emptyBlock(t *testing.T) {
checkFunc := func(s *terraform.State) error {
root := s.ModuleByPath(addrs.RootModuleInstance)
res := root.Resources["test_resource_nested_set.foo"]
for k, v := range res.Primary.Attributes {
if strings.HasPrefix(k, "type_list") && v != "1" {
return fmt.Errorf("unexpected set value: %s:%s", k, v)
}
}
return nil
}
resource.UnitTest(t, resource.TestCase{ resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckResourceDestroy, CheckDestroy: testAccCheckResourceDestroy,
@ -80,7 +70,9 @@ resource "test_resource_nested_set" "foo" {
} }
} }
`), `),
Check: checkFunc, Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("test_resource_nested_set.foo", "type_list.#", "1"),
),
}, },
}, },
}) })

View File

@ -713,17 +713,27 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A
} }
} }
// strip out non-diffs
for k, v := range diff.Attributes {
if v.New == v.Old && !v.NewComputed && v.NewExtra == "" {
delete(diff.Attributes, k)
}
}
if private != nil { if private != nil {
diff.Meta = private diff.Meta = private
} }
// We need to turn off any RequiresNew. There could be attributes
// without changes in here inserted by helper/schema, but if they have
// RequiresNew then the state will will be dropped from the ResourceData.
for k := range diff.Attributes {
diff.Attributes[k].RequiresNew = false
}
// check that any "removed" attributes actually exist in the prior state, or
// helper/schema will confuse itself
for k, d := range diff.Attributes {
if d.NewRemoved {
if _, ok := priorState.Attributes[k]; !ok {
delete(diff.Attributes, k)
}
}
}
newInstanceState, err := s.provider.Apply(info, priorState, diff) newInstanceState, err := s.provider.Apply(info, priorState, diff)
if err != nil { if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err) resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"log"
"reflect" "reflect"
"regexp" "regexp"
"sort" "sort"
@ -494,11 +495,12 @@ func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, sc
// we need to find the set of all keys that traverse this block // we need to find the set of all keys that traverse this block
candidateKeys := map[string]bool{} candidateKeys := map[string]bool{}
blockKey := blockPrefix + n + "." blockKey := blockPrefix + n + "."
localBlockPrefix := localPrefix + n + "."
// we can only trust the diff for sets, since the path changes, so don't // we can only trust the diff for sets, since the path changes, so don't
// count existing values as candidate keys. If it turns out we're // count existing values as candidate keys. If it turns out we're
// keeping the attributes, we will check catch it down below with // keeping the attributes, we will catch it down below with "keepBlock"
// "keepBlock" after we check the set count. // after we check the set count.
if block.Nesting != configschema.NestingSet { if block.Nesting != configschema.NestingSet {
for k := range attrs { for k := range attrs {
if strings.HasPrefix(k, blockKey) { if strings.HasPrefix(k, blockKey) {
@ -570,7 +572,7 @@ func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, sc
} }
for attr, v := range newAttrs { for attr, v := range newAttrs {
result[localPrefix+n+"."+attr] = v result[localBlockPrefix+attr] = v
} }
} }
@ -589,24 +591,51 @@ func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, sc
if strings.HasPrefix(k, blockKey) { if strings.HasPrefix(k, blockKey) {
// we need the key relative to this block, so remove the // we need the key relative to this block, so remove the
// entire prefix, then re-insert the block name. // entire prefix, then re-insert the block name.
localKey := n + "." + k[len(blockKey):] localKey := localBlockPrefix + k[len(blockKey):]
result[localKey] = v result[localKey] = v
} }
} }
} }
if countDiff, ok := d.Attributes[strings.Join(append(path, n, ".#"), ".")]; ok { if countDiff, ok := d.Attributes[strings.Join(append(path, n, "#"), ".")]; ok {
if countDiff.NewComputed { if countDiff.NewComputed {
result[localPrefix+n+".#"] = hcl2shim.UnknownVariableValue result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue
} else { } else {
result[localPrefix+n+".#"] = countDiff.New result[localBlockPrefix+"#"] = countDiff.New
// While sets are complete, list are not, and we may not have all the
// information to track removals. If the list was truncated, we need to
// remove the extra items from the result.
if block.Nesting == configschema.NestingList &&
countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue {
length, _ := strconv.Atoi(countDiff.New)
for k := range result {
if !strings.HasPrefix(k, localBlockPrefix) {
continue
}
index := k[len(localBlockPrefix):]
nextDot := strings.Index(index, ".")
if nextDot < 1 {
continue
}
index = index[:nextDot]
i, err := strconv.Atoi(index)
if err != nil {
// this shouldn't happen since we added these
// ourself, but make note of it just in case.
log.Printf("[ERROR] bas list index in %q: %s", k, err)
continue
}
if i >= length {
delete(result, k)
}
}
}
} }
} else { } else {
result[localPrefix+n+".#"] = countFlatmapContainerValues(localPrefix+n+".#", result) result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result)
} }
} }
return result, nil return result, nil