diff --git a/internal/plans/objchange/plan_valid.go b/internal/plans/objchange/plan_valid.go index 2706a6c52..4f35f5347 100644 --- a/internal/plans/objchange/plan_valid.go +++ b/internal/plans/objchange/plan_valid.go @@ -286,11 +286,6 @@ func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, pla } return errs } - } else { - if attrS.Computed { - errs = append(errs, path.NewErrorf("configuration present for computed attribute")) - return errs - } } // If this attribute has a NestedType, validate the nested object diff --git a/internal/plans/objchange/plan_valid_test.go b/internal/plans/objchange/plan_valid_test.go index 834d10463..5c054cb90 100644 --- a/internal/plans/objchange/plan_valid_test.go +++ b/internal/plans/objchange/plan_valid_test.go @@ -1387,6 +1387,7 @@ func TestAssertPlanValid(t *testing.T) { }, }, }, + Optional: true, Computed: true, }, "single": { @@ -1423,9 +1424,11 @@ func TestAssertPlanValid(t *testing.T) { "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ "name": cty.String, }))), - "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ - "name": cty.String, - }))), + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), + }), "single": cty.NullVal(cty.Object(map[string]cty.Type{ "name": cty.String, })), @@ -1437,14 +1440,14 @@ func TestAssertPlanValid(t *testing.T) { })), }), "list": cty.ListVal([]cty.Value{ - cty.UnknownVal(cty.Object(map[string]cty.Type{ - "name": cty.String, - })), + cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("computed"), + }), }), "set": cty.SetVal([]cty.Value{ - cty.UnknownVal(cty.Object(map[string]cty.Type{ - "name": cty.String, - })), + cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), }), "single": cty.UnknownVal(cty.Object(map[string]cty.Type{ "name": cty.String, @@ -1452,6 +1455,179 @@ func TestAssertPlanValid(t *testing.T) { }), nil, }, + "optional computed within nested objects": { + &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "map": { + NestedType: &configschema.Object{ + Nesting: configschema.NestingMap, + Attributes: map[string]*configschema.Attribute{ + "name": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + // When an object has dynamic attrs, the map may be + // handled as an object. + "map_as_obj": { + NestedType: &configschema.Object{ + Nesting: configschema.NestingMap, + Attributes: map[string]*configschema.Attribute{ + "name": { + Type: cty.String, + Optional: true, + Computed: true, + }, + }, + }, + }, + "list": { + NestedType: &configschema.Object{ + Nesting: configschema.NestingList, + Attributes: map[string]*configschema.Attribute{ + "name": { + Type: cty.String, + Optional: true, + Computed: true, + }, + }, + }, + }, + "set": { + NestedType: &configschema.Object{ + Nesting: configschema.NestingSet, + Attributes: map[string]*configschema.Attribute{ + "name": { + Type: cty.String, + Optional: true, + Computed: true, + }, + }, + }, + }, + "single": { + NestedType: &configschema.Object{ + Nesting: configschema.NestingSingle, + Attributes: map[string]*configschema.Attribute{ + "name": { + Type: cty.DynamicPseudoType, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + cty.NullVal(cty.Object(map[string]cty.Type{ + "map": cty.Map(cty.Object(map[string]cty.Type{ + "name": cty.String, + })), + "map_as_obj": cty.Map(cty.Object(map[string]cty.Type{ + "name": cty.DynamicPseudoType, + })), + "list": cty.List(cty.Object(map[string]cty.Type{ + "name": cty.String, + })), + "set": cty.Set(cty.Object(map[string]cty.Type{ + "name": cty.String, + })), + "single": cty.Object(map[string]cty.Type{ + "name": cty.String, + }), + })), + cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "one": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), + }), + "map_as_obj": cty.MapVal(map[string]cty.Value{ + "one": cty.ObjectVal(map[string]cty.Value{ + "name": cty.NullVal(cty.DynamicPseudoType), + }), + }), + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "name": cty.NullVal(cty.String), + }), + }), + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "name": cty.NullVal(cty.String), + }), + }), + "single": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "one": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), + }), + "map_as_obj": cty.ObjectVal(map[string]cty.Value{ + "one": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("computed"), + }), + }), + "list": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("computed"), + }), + }), + "set": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "name": cty.NullVal(cty.String), + }), + }), + "single": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), + }), + nil, + }, + "cannot replace config nested attr": { + &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "map": { + NestedType: &configschema.Object{ + Nesting: configschema.NestingMap, + Attributes: map[string]*configschema.Attribute{ + "name": { + Type: cty.String, + Computed: true, + Optional: true, + }, + }, + }, + }, + }, + }, + cty.NullVal(cty.Object(map[string]cty.Type{ + "map": cty.Map(cty.Object(map[string]cty.Type{ + "name": cty.String, + })), + })), + cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "one": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_config"), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "map": cty.MapVal(map[string]cty.Value{ + "one": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("from_provider"), + }), + }), + }), + []string{`.map.one.name: planned value cty.StringVal("from_provider") does not match config value cty.StringVal("from_config")`}, + }, } for name, test := range tests {