From 9593fd96e382b5f250ae089ad792caa9d3e2f262 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Tue, 28 Sep 2021 17:54:29 -0400 Subject: [PATCH 1/4] tfsdk: More exhaustive testing and TODOs for (Config).GetAttribute() --- tfsdk/config_test.go | 907 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 891 insertions(+), 16 deletions(-) diff --git a/tfsdk/config_test.go b/tfsdk/config_test.go index 4524c5083..065c54a01 100644 --- a/tfsdk/config_test.go +++ b/tfsdk/config_test.go @@ -157,72 +157,944 @@ func TestConfigGetAttribute(t *testing.T) { type testCase struct { config Config + path *tftypes.AttributePath expected attr.Value expectedDiags diag.Diagnostics } testCases := map[string]testCase{ - "basic": { + "empty": { config: Config{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, nil), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: nil, + }, + "WithAttributeName-nonexistent": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("other"), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("other"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "AttributeName(\"other\") still remains in the path: could not find attribute \"other\" in schema", + ), + }, + }, + "WithAttributeName-List-null-WithElementKeyInt": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyInt(0) still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-List-WithElementKeyInt": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.String, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.String, "value"), + tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-ListNestedAttributes-null-WithElementKeyInt-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, ListNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyInt(0).AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyInt-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, ListNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Map-null-WithElementKeyString": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyString(\"sub_test\") still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-Map-WithElementKeyString": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Map-WithElementKeyString-nonexistent": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("other"), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("other"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyString(\"other\") still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-MapNestedAttributes-null-WithElementKeyInt-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, MapNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyString(\"element\").AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyString-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, map[string]tftypes.Value{ + "element": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, MapNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Object-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "sub_test": types.StringType, + }, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Set-null-WithElementKeyValue": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyValue(tftypes.String<\"value\">) still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-Set-WithElementKeyValue": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.String, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.String, "value"), + tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-SetNestedAttributes-null-WithElementKeyValue-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, SetNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "ElementKeyValue(tftypes.Object[\"sub_test\":tftypes.String]<\"sub_test\":tftypes.String<\"value\">>).AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-SetNestedAttributes-WithElementKeyValue-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, SetNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-SingleNestedAttributes-null-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: nil, + // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", + ), + }, + }, + "WithAttributeName-SingleNestedAttributes-WithAttributeName": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-String-null": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { Type: types.StringType, Required: true, }, + "other": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-String-unknown": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Unknown: true}, + }, + "WithAttributeName-String-value": { + config: Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - expected: types.String{Value: "namevalue"}, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Value: "value"}, }, "AttrTypeWithValidateError": { config: Config{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: testtypes.StringTypeWithValidateError{}, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, + path: tftypes.NewAttributePath().WithAttributeName("test"), expected: nil, - expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("name"))}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"))}, }, "AttrTypeWithValidateWarning": { config: Config{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: testtypes.StringTypeWithValidateWarning{}, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - expected: testtypes.String{String: types.String{Value: "namevalue"}, CreatedBy: testtypes.StringTypeWithValidateWarning{}}, - expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("name"))}, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: testtypes.String{String: types.String{Value: "value"}, CreatedBy: testtypes.StringTypeWithValidateWarning{}}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"))}, }, } @@ -231,9 +1103,12 @@ func TestConfigGetAttribute(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - val, diags := tc.config.GetAttribute(context.Background(), tftypes.NewAttributePath().WithAttributeName("name")) - + val, diags := tc.config.GetAttribute(context.Background(), tc.path) if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + for _, d := range diags { + t.Log(d.Summary()) + t.Log(d.Detail()) + } t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } From 6088f70169d23ee5c17864b153f374b53ce52098 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 29 Sep 2021 11:08:08 -0400 Subject: [PATCH 2/4] tfsdk: Ignore ErrInvalidStep in (Config).GetAttribute() --- tfsdk/attribute_test.go | 27 ++++++------ tfsdk/config.go | 5 ++- tfsdk/config_test.go | 94 ++++------------------------------------- 3 files changed, 27 insertions(+), 99 deletions(-) diff --git a/tfsdk/attribute_test.go b/tfsdk/attribute_test.go index 39f3b807e..cef1338d1 100644 --- a/tfsdk/attribute_test.go +++ b/tfsdk/attribute_test.go @@ -708,15 +708,15 @@ func TestAttributeModifyPlan(t *testing.T) { Config: Config{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -766,7 +766,8 @@ func TestAttributeModifyPlan(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, @@ -777,15 +778,15 @@ func TestAttributeModifyPlan(t *testing.T) { Config: Config{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -845,7 +846,8 @@ func TestAttributeModifyPlan(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, @@ -2151,15 +2153,15 @@ func TestAttributeValidate(t *testing.T) { Config: Config{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -2171,7 +2173,8 @@ func TestAttributeValidate(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, diff --git a/tfsdk/config.go b/tfsdk/config.go index 40ea23061..2b3e5eb48 100644 --- a/tfsdk/config.go +++ b/tfsdk/config.go @@ -2,6 +2,7 @@ package tfsdk import ( "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -44,7 +45,9 @@ func (c Config) GetAttribute(ctx context.Context, path *tftypes.AttributePath) ( } tfValue, err := c.terraformValueAtPath(path) - if err != nil { + + // Ignoring ErrInvalidStep will allow this method to return a null value of the type. + if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { diags.AddAttributeError( path, "Configuration Read Error", diff --git a/tfsdk/config_test.go b/tfsdk/config_test.go index 065c54a01..0138b88d2 100644 --- a/tfsdk/config_test.go +++ b/tfsdk/config_test.go @@ -247,16 +247,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyInt(0) still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-List-WithElementKeyInt": { config: Config{ @@ -336,16 +327,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyInt(0).AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-ListNestedAttributes-WithElementKeyInt-WithAttributeName": { config: Config{ @@ -430,16 +412,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyString(\"sub_test\") still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-Map-WithElementKeyString": { config: Config{ @@ -510,16 +483,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("other"), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("other"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyString(\"other\") still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-MapNestedAttributes-null-WithElementKeyInt-WithAttributeName": { config: Config{ @@ -563,16 +527,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyString(\"element\").AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-MapNestedAttributes-WithElementKeyString-WithAttributeName": { config: Config{ @@ -698,16 +653,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyValue(tftypes.String<\"value\">) still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-Set-WithElementKeyValue": { config: Config{ @@ -793,22 +739,7 @@ func TestConfigGetAttribute(t *testing.T) { }, map[string]tftypes.Value{ "sub_test": tftypes.NewValue(tftypes.String, "value"), })).WithAttributeName("sub_test"), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "sub_test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "sub_test": tftypes.NewValue(tftypes.String, "value"), - })).WithAttributeName("sub_test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "ElementKeyValue(tftypes.Object[\"sub_test\":tftypes.String]<\"sub_test\":tftypes.String<\"value\">>).AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-SetNestedAttributes-WithElementKeyValue-WithAttributeName": { config: Config{ @@ -906,16 +837,7 @@ func TestConfigGetAttribute(t *testing.T) { }, }, path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), - expected: nil, - // TODO: https://github.com/hashicorp/terraform-plugin-framework/issues/150 - expectedDiags: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "AttributeName(\"sub_test\") still remains in the path: step cannot be applied to this value", - ), - }, + expected: types.String{Null: true}, }, "WithAttributeName-SingleNestedAttributes-WithAttributeName": { config: Config{ From 5d650f2d1d92d8c5b7f2438fafbccff8cf47081d Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 29 Sep 2021 11:13:04 -0400 Subject: [PATCH 3/4] Update CHANGELOG for #185 --- .changelog/185.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/185.txt diff --git a/.changelog/185.txt b/.changelog/185.txt new file mode 100644 index 000000000..b5768d767 --- /dev/null +++ b/.changelog/185.txt @@ -0,0 +1,3 @@ +```release-note:bug +tfsdk: Fetch null values from valid missing `Config`, `Plan`, and `State` paths in `GetAttribute()` method +``` From 9de09d6fbf0301db3ebf32cb2d107953e2ca1981 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 29 Sep 2021 12:23:38 -0400 Subject: [PATCH 4/4] tfsdk: Replicate Config GetAttribute changes to Plan and State, add TODOs for unknown handling --- tfsdk/attribute_test.go | 36 +- tfsdk/config.go | 4 + tfsdk/config_test.go | 4 - tfsdk/plan.go | 8 +- tfsdk/plan_test.go | 825 +++++++++++++++++++++++++++++++++++- tfsdk/state.go | 8 +- tfsdk/state_test.go | 919 +++++++++++++++++++++++++++++----------- 7 files changed, 1526 insertions(+), 278 deletions(-) diff --git a/tfsdk/attribute_test.go b/tfsdk/attribute_test.go index cef1338d1..c22ee81a5 100644 --- a/tfsdk/attribute_test.go +++ b/tfsdk/attribute_test.go @@ -875,15 +875,15 @@ func TestAttributeModifyPlan(t *testing.T) { Plan: Plan{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -916,7 +916,8 @@ func TestAttributeModifyPlan(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "Plan Read Error", - "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, @@ -944,15 +945,15 @@ func TestAttributeModifyPlan(t *testing.T) { Plan: Plan{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -995,7 +996,8 @@ func TestAttributeModifyPlan(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "Plan Read Error", - "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, @@ -1040,15 +1042,15 @@ func TestAttributeModifyPlan(t *testing.T) { State: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -1064,7 +1066,8 @@ func TestAttributeModifyPlan(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "State Read Error", - "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, @@ -1109,15 +1112,15 @@ func TestAttributeModifyPlan(t *testing.T) { State: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "nottest": tftypes.String, + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "nottest": tftypes.NewValue(tftypes.String, "testvalue"), + "test": tftypes.NewValue(tftypes.String, "testvalue"), }), Schema: Schema{ Attributes: map[string]Attribute{ "test": { - Type: types.StringType, + Type: types.ListType{ElemType: types.StringType}, Required: true, }, }, @@ -1143,7 +1146,8 @@ func TestAttributeModifyPlan(t *testing.T) { diag.NewAttributeErrorDiagnostic( tftypes.NewAttributePath().WithAttributeName("test"), "State Read Error", - "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\nAttributeName(\"test\") still remains in the path: step cannot be applied to this value", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", ), }, }, diff --git a/tfsdk/config.go b/tfsdk/config.go index 2b3e5eb48..38497d5b6 100644 --- a/tfsdk/config.go +++ b/tfsdk/config.go @@ -56,6 +56,10 @@ func (c Config) GetAttribute(ctx context.Context, path *tftypes.AttributePath) ( return nil, diags } + // TODO: If ErrInvalidStep, check parent paths for unknown value. + // If found, convert this value to an unknown value. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/186 + if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path)...) diff --git a/tfsdk/config_test.go b/tfsdk/config_test.go index 0138b88d2..f3604e9ce 100644 --- a/tfsdk/config_test.go +++ b/tfsdk/config_test.go @@ -1027,10 +1027,6 @@ func TestConfigGetAttribute(t *testing.T) { val, diags := tc.config.GetAttribute(context.Background(), tc.path) if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { - for _, d := range diags { - t.Log(d.Summary()) - t.Log(d.Detail()) - } t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } diff --git a/tfsdk/plan.go b/tfsdk/plan.go index a5e6ec804..9e1260433 100644 --- a/tfsdk/plan.go +++ b/tfsdk/plan.go @@ -45,7 +45,9 @@ func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (at } tfValue, err := p.terraformValueAtPath(path) - if err != nil { + + // Ignoring ErrInvalidStep will allow this method to return a null value of the type. + if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { diags.AddAttributeError( path, "Plan Read Error", @@ -54,6 +56,10 @@ func (p Plan) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (at return nil, diags } + // TODO: If ErrInvalidStep, check parent paths for unknown value. + // If found, convert this value to an unknown value. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/186 + if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path)...) diff --git a/tfsdk/plan_test.go b/tfsdk/plan_test.go index ed4f26b2c..bb51df089 100644 --- a/tfsdk/plan_test.go +++ b/tfsdk/plan_test.go @@ -157,72 +157,866 @@ func TestPlanGetAttribute(t *testing.T) { type testCase struct { plan Plan + path *tftypes.AttributePath expected attr.Value expectedDiags diag.Diagnostics } testCases := map[string]testCase{ - "basic": { + "empty": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, nil), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: nil, + }, + "WithAttributeName-nonexistent": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "value"), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("other"), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("other"), + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "error getting attribute type in schema: AttributeName(\"other\") still remains in the path: could not find attribute \"other\" in schema", + ), + }, + }, + "WithAttributeName-List-null-WithElementKeyInt": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: types.String{Null: true}, + }, + "WithAttributeName-List-WithElementKeyInt": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.String, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.String, "value"), + tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-ListNestedAttributes-null-WithElementKeyInt-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, ListNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyInt-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, ListNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Map-null-WithElementKeyString": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-Map-WithElementKeyString": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Map-WithElementKeyString-nonexistent": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("other"), + expected: types.String{Null: true}, + }, + "WithAttributeName-MapNestedAttributes-null-WithElementKeyInt-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, MapNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyString-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, map[string]tftypes.Value{ + "element": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, MapNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Object-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "sub_test": types.StringType, + }, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Set-null-WithElementKeyValue": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + expected: types.String{Null: true}, + }, + "WithAttributeName-Set-WithElementKeyValue": { plan: Plan{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.Set{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.String, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.String, "value"), + tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-SetNestedAttributes-null-WithElementKeyValue-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, SetNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-SetNestedAttributes-WithElementKeyValue-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, SetNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-SingleNestedAttributes-null-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-SingleNestedAttributes-WithAttributeName": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-String-null": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-String-unknown": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Unknown: true}, + }, + "WithAttributeName-String-value": { + plan: Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { Type: types.StringType, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - expected: types.String{Value: "namevalue"}, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Value: "value"}, }, "AttrTypeWithValidateError": { plan: Plan{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: testtypes.StringTypeWithValidateError{}, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, + path: tftypes.NewAttributePath().WithAttributeName("test"), expected: nil, - expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("name"))}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"))}, }, "AttrTypeWithValidateWarning": { plan: Plan{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: testtypes.StringTypeWithValidateWarning{}, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - expected: testtypes.String{String: types.String{Value: "namevalue"}, CreatedBy: testtypes.StringTypeWithValidateWarning{}}, - expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("name"))}, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: testtypes.String{String: types.String{Value: "value"}, CreatedBy: testtypes.StringTypeWithValidateWarning{}}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"))}, }, } @@ -231,8 +1025,7 @@ func TestPlanGetAttribute(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - val, diags := tc.plan.GetAttribute(context.Background(), tftypes.NewAttributePath().WithAttributeName("name")) - + val, diags := tc.plan.GetAttribute(context.Background(), tc.path) if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) } diff --git a/tfsdk/state.go b/tfsdk/state.go index 74af7d4a8..7e5ef13b7 100644 --- a/tfsdk/state.go +++ b/tfsdk/state.go @@ -45,7 +45,9 @@ func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (a } tfValue, err := s.terraformValueAtPath(path) - if err != nil { + + // Ignoring ErrInvalidStep will allow this method to return a null value of the type. + if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { diags.AddAttributeError( path, "State Read Error", @@ -54,6 +56,10 @@ func (s State) GetAttribute(ctx context.Context, path *tftypes.AttributePath) (a return nil, diags } + // TODO: If ErrInvalidStep, check parent paths for unknown value. + // If found, convert this value to an unknown value. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/186 + if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path)...) diff --git a/tfsdk/state_test.go b/tfsdk/state_test.go index 0c42a2d3b..52bc5c4cf 100644 --- a/tfsdk/state_test.go +++ b/tfsdk/state_test.go @@ -860,420 +860,860 @@ func TestStateGetAttribute(t *testing.T) { } testCases := map[string]testCase{ - "primitive": { + "empty": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, + }, + }, nil), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: nil, + }, + "WithAttributeName-nonexistent": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "hello, world"), + "test": tftypes.NewValue(tftypes.String, "value"), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: types.StringType, Required: true, }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("name"), - expected: types.String{Value: "hello, world"}, + path: tftypes.NewAttributePath().WithAttributeName("other"), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("other"), + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "error getting attribute type in schema: AttributeName(\"other\") still remains in the path: could not find attribute \"other\" in schema", + ), + }, }, - "list": { + "WithAttributeName-List-null-WithElementKeyInt": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: types.String{Null: true}, + }, + "WithAttributeName-List-WithElementKeyInt": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.String, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.String, "value"), + tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-ListNestedAttributes-null-WithElementKeyInt-WithAttributeName": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, ListNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-ListNestedAttributes-WithElementKeyInt-WithAttributeName": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "tags": tftypes.List{ElementType: tftypes.String}, + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: ListNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, ListNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Map-null-WithElementKeyString": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-Map-WithElementKeyString": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Map-WithElementKeyString-nonexistent": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.String, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("other"), + expected: types.String{Null: true}, + }, + "WithAttributeName-MapNestedAttributes-null-WithElementKeyInt-WithAttributeName": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, MapNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: types.String{Null: true}, + }, + "WithAttributeName-MapNestedAttributes-WithElementKeyString-WithAttributeName": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, map[string]tftypes.Value{ + "element": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Attributes: MapNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, MapNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyString("element").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Object-WithAttributeName": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "sub_test": types.StringType, + }, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-Set-null-WithElementKeyValue": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.String, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + expected: types.String{Null: true}, + }, + "WithAttributeName-Set-WithElementKeyValue": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.String, + }, + "other": tftypes.Bool, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.String, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.String, "value"), + tftypes.NewValue(tftypes.String, "othervalue"), + }), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.String, "value")), + expected: types.String{Value: "value"}, + }, + "WithAttributeName-SetNestedAttributes-null-WithElementKeyValue-WithAttributeName": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "tags": tftypes.NewValue(tftypes.List{ - ElementType: tftypes.String, - }, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "blue"), - tftypes.NewValue(tftypes.String, "green"), - }), + "test": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, + }, + }, + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "tags": { - Type: types.ListType{ - ElemType: types.StringType, - }, + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, + }, + }, SetNestedAttributesOptions{}), Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("tags"), - expected: types.List{ - Elems: []attr.Value{ - types.String{Value: "red"}, - types.String{Value: "blue"}, - types.String{Value: "green"}, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, }, - ElemType: types.StringType, - }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + expected: types.String{Null: true}, }, - "nested-list": { + "WithAttributeName-SetNestedAttributes-WithElementKeyValue-WithAttributeName": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "disks": tftypes.List{ + "test": tftypes.Set{ ElementType: tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, + "sub_test": tftypes.String, }, }, }, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "disks": tftypes.NewValue(tftypes.List{ + "test": tftypes.NewValue(tftypes.Set{ ElementType: tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, + "sub_test": tftypes.String, }, }, }, []tftypes.Value{ tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "disk0"), - "delete_with_instance": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, + "sub_test": tftypes.String, }, }, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "disk1"), - "delete_with_instance": tftypes.NewValue(tftypes.Bool, false), + "sub_test": tftypes.NewValue(tftypes.String, "value"), }), }), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "disks": { - Attributes: ListNestedAttributes(map[string]Attribute{ - "id": { + "test": { + Attributes: SetNestedAttributes(map[string]Attribute{ + "sub_test": { Type: types.StringType, Required: true, }, - "delete_with_instance": { - Type: types.BoolType, - Optional: true, - }, - }, ListNestedAttributesOptions{}), + }, SetNestedAttributesOptions{}), + Required: true, + }, + "other": { + Type: types.BoolType, Optional: true, - Computed: true, }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("disks"), - expected: types.List{ - Elems: []attr.Value{ - types.Object{ - Attrs: map[string]attr.Value{ - "delete_with_instance": types.Bool{Value: true}, - "id": types.String{Value: "disk0"}, - }, - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, - }, - }, - types.Object{ - Attrs: map[string]attr.Value{ - "delete_with_instance": types.Bool{Value: false}, - "id": types.String{Value: "disk1"}, - }, - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, - }, - }, - }, - ElemType: types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, - }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyValue(tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "sub_test": tftypes.String, }, - }, + }, map[string]tftypes.Value{ + "sub_test": tftypes.NewValue(tftypes.String, "value"), + })).WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, }, - "nested-single": { + "WithAttributeName-SingleNestedAttributes-null-WithAttributeName": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "boot_disk": tftypes.Object{ + "test": tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, + "sub_test": tftypes.String, }, }, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "boot_disk": tftypes.NewValue(tftypes.Object{ + "test": tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, + "sub_test": tftypes.String, }, - }, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "bootdisk"), - "delete_with_instance": tftypes.NewValue(tftypes.Bool, true), - }), + }, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "boot_disk": { + "test": { Attributes: SingleNestedAttributes(map[string]Attribute{ - "id": { + "sub_test": { Type: types.StringType, Required: true, }, - "delete_with_instance": { - Type: types.BoolType, - }, }), + Required: true, + }, + "other": { + Type: types.BoolType, + Optional: true, }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("boot_disk"), - expected: types.Object{ - Attrs: map[string]attr.Value{ - "delete_with_instance": types.Bool{Value: true}, - "id": types.String{Value: "bootdisk"}, - }, - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, - }, - }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Null: true}, }, - "object": { + "WithAttributeName-SingleNestedAttributes-WithAttributeName": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "scratch_disk": tftypes.Object{ + "test": tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "interface": tftypes.String, + "sub_test": tftypes.String, }, }, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "scratch_disk": tftypes.NewValue(tftypes.Object{ + "test": tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "interface": tftypes.String, + "sub_test": tftypes.String, }, }, map[string]tftypes.Value{ - "interface": tftypes.NewValue(tftypes.String, "SCSI"), + "sub_test": tftypes.NewValue(tftypes.String, "value"), }), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "scratch_disk": { - Type: types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "interface": types.StringType, + "test": { + Attributes: SingleNestedAttributes(map[string]Attribute{ + "sub_test": { + Type: types.StringType, + Required: true, }, - }, + }), + Required: true, + }, + "other": { + Type: types.BoolType, Optional: true, }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("scratch_disk"), - expected: types.Object{ - Attrs: map[string]attr.Value{ - "interface": types.String{Value: "SCSI"}, - }, - AttrTypes: map[string]attr.Type{ - "interface": types.StringType, - }, - }, + path: tftypes.NewAttributePath().WithAttributeName("test").WithAttributeName("sub_test"), + expected: types.String{Value: "value"}, }, - "set": { + "WithAttributeName-String-null": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "tags": tftypes.Set{ElementType: tftypes.String}, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "tags": tftypes.NewValue(tftypes.Set{ - ElementType: tftypes.String, - }, []tftypes.Value{ - tftypes.NewValue(tftypes.String, "red"), - tftypes.NewValue(tftypes.String, "blue"), - tftypes.NewValue(tftypes.String, "green"), - }), + "test": tftypes.NewValue(tftypes.String, nil), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "tags": { - Type: types.SetType{ - ElemType: types.StringType, - }, + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.StringType, Required: true, }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("tags"), - expected: types.Set{ - Elems: []attr.Value{ - types.String{Value: "red"}, - types.String{Value: "blue"}, - types.String{Value: "green"}, - }, - ElemType: types.StringType, - }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Null: true}, }, - "nested-set": { + "WithAttributeName-String-unknown": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "disks": tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, - }, - }, - }, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "disks": tftypes.NewValue(tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, - }, - }, - }, []tftypes.Value{ - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "disk0"), - "delete_with_instance": tftypes.NewValue(tftypes.Bool, true), - }), - tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "delete_with_instance": tftypes.Bool, - }, - }, map[string]tftypes.Value{ - "id": tftypes.NewValue(tftypes.String, "disk1"), - "delete_with_instance": tftypes.NewValue(tftypes.Bool, false), - }), - }), + "test": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "disks": { - Attributes: SetNestedAttributes(map[string]Attribute{ - "id": { - Type: types.StringType, - Required: true, - }, - "delete_with_instance": { - Type: types.BoolType, - Optional: true, - }, - }, SetNestedAttributesOptions{}), + "test": { + Type: types.StringType, + Required: true, + }, + "other": { + Type: types.BoolType, Optional: true, - Computed: true, }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("disks"), - expected: types.Set{ - Elems: []attr.Value{ - types.Object{ - Attrs: map[string]attr.Value{ - "delete_with_instance": types.Bool{Value: true}, - "id": types.String{Value: "disk0"}, - }, - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, - }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Unknown: true}, + }, + "WithAttributeName-String-value": { + state: State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + "other": tftypes.Bool, }, - types.Object{ - Attrs: map[string]attr.Value{ - "delete_with_instance": types.Bool{Value: false}, - "id": types.String{Value: "disk1"}, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), + }), + Schema: Schema{ + Attributes: map[string]Attribute{ + "test": { + Type: types.StringType, + Required: true, }, - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, + "other": { + Type: types.BoolType, + Optional: true, }, }, }, - ElemType: types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "delete_with_instance": types.BoolType, - "id": types.StringType, - }, - }, }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: types.String{Value: "value"}, }, "AttrTypeWithValidateError": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: testtypes.StringTypeWithValidateError{}, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("name"), + path: tftypes.NewAttributePath().WithAttributeName("test"), expected: nil, - expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("name"))}, + expectedDiags: diag.Diagnostics{testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"))}, }, "AttrTypeWithValidateWarning": { state: State{ Raw: tftypes.NewValue(tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ - "name": tftypes.String, + "test": tftypes.String, + "other": tftypes.Bool, }, }, map[string]tftypes.Value{ - "name": tftypes.NewValue(tftypes.String, "namevalue"), + "test": tftypes.NewValue(tftypes.String, "value"), + "other": tftypes.NewValue(tftypes.Bool, nil), }), Schema: Schema{ Attributes: map[string]Attribute{ - "name": { + "test": { Type: testtypes.StringTypeWithValidateWarning{}, Required: true, }, + "other": { + Type: types.BoolType, + Optional: true, + }, }, }, }, - path: tftypes.NewAttributePath().WithAttributeName("name"), - expected: testtypes.String{String: types.String{Value: "namevalue"}, CreatedBy: testtypes.StringTypeWithValidateWarning{}}, - expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("name"))}, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: testtypes.String{String: types.String{Value: "value"}, CreatedBy: testtypes.StringTypeWithValidateWarning{}}, + expectedDiags: diag.Diagnostics{testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test"))}, }, } @@ -1283,7 +1723,6 @@ func TestStateGetAttribute(t *testing.T) { t.Parallel() val, diags := tc.state.GetAttribute(context.Background(), tc.path) - if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { t.Errorf("unexpected diagnostics (+wanted, -got): %s", diff) }