Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions internal/plans/objchange/plan_valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,20 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
// Easy path: nothing has changed at all
continue
}

if !configV.IsKnown() {
// An unknown config block represents a dynamic block where the
// for_each value is unknown, and therefor cannot be altered by the
// provider.
errs = append(errs, path.NewErrorf("planned value %#v for unknown dynamic block", plannedV))
continue
}

if !plannedV.IsKnown() {
// Only dynamic configuration can set blocks to unknown, so this is
// not allowed from the provider. This means that either the config
// and plan should match, or we have an error where the plan
// changed the config value, both of which have been checked.
errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead"))
continue
}
Expand All @@ -94,6 +107,7 @@ func assertPlanValid(schema *configschema.Block, priorState, config, plannedStat
errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL))
continue
}

for it := plannedV.ElementIterator(); it.Next(); {
idx, plannedEV := it.Element()
path := append(path, cty.IndexStep{Key: idx})
Expand Down
104 changes: 104 additions & 0 deletions internal/plans/objchange/plan_valid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,110 @@ func TestAssertPlanValid(t *testing.T) {
`.b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null`,
},
},

// blocks can be unknown when using dynamic
"nested list, unknown nested dynamic": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"a": {
Nesting: configschema.NestingList,
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"b": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"c": {
Type: cty.String,
Optional: true,
},
"computed": {
Type: cty.String,
Computed: true,
},
},
},
},
},
},
},
},
},

cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"computed": cty.NullVal(cty.String),
"b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"c": cty.StringVal("x"),
})}),
})}),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
"c": cty.String,
"computed": cty.String,
}))),
})}),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
"c": cty.String,
"computed": cty.String,
}))),
})}),
}),
[]string{},
},

"nested set, unknown dynamic cannot be planned": {
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"computed": {
Type: cty.String,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"b": {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"c": {
Type: cty.String,
Optional: true,
},
},
},
},
},
},

cty.ObjectVal(map[string]cty.Value{
"computed": cty.NullVal(cty.String),
"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"c": cty.StringVal("x"),
})}),
}),
cty.ObjectVal(map[string]cty.Value{
"computed": cty.NullVal(cty.String),
"b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
"c": cty.String,
}))),
}),
cty.ObjectVal(map[string]cty.Value{
"computed": cty.StringVal("default"),
"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"c": cty.StringVal("oops"),
})}),
}),

[]string{
`.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`,
},
},

"nested set, null in plan": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
Expand Down