How to tell if sub-structure attributes are set in the resource

Is there a way to check inside my Go provider whether or not an attribute under a structure has been set in the resource? For some reason when I get the map[string]interface{}, all keys are set with default values if they are not specifically set in the resource.

With an example schema:

“test”: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Description: “”,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
“myval”: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: “”,
},
“mystr”: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: “”,
},
},
},
},

And my simple resource:

resource “test” “test” {
test {
mystr = “one”
}
}

Then in my go code, I just do

test := d.Get(“test”).(interface{})
obj := test[0].(map[string]interface{})

obj contains both keys, mystr hsd the “one” value set and the myval key is an empty string:
map[mystr:one myval:]

So is there a d.GetOK like method I can call to see if these sub attributes are set in the resource or not?

Thanks

Hi @cwsunderland,

It looks like you are implementing your provider with SDKv2. Unfortunately that SDK was originally designed around the assumptions of Terraform v0.11 and earlier, which had a more rudimentary type system which didn’t have any general way to model something being “unset”. Although in this particular case it could perhaps have left certain elements absent completely in the map, the underlying logic for choosing defaults here is more general than just for nested blocks and applies the same rules in all cases so that provider developers only have one set of rules to worry about.

If it’s important for you to be able to recognise the difference between an argument being unset or being set to its default value, I think you will have more success using the modern Plugin Framework, which is designed for modern Terraform. The framework will give you a null value to represent something being unset, which you can then handle in whichever way is appropriate for your provider.

There are some mechanisms like GetOk which were earlier attempts to make this capability available in SDKv2, but they cannot work reliably because the underlying data structures in the SDK’s internals don’t consistently track the difference between unset and default themselves; those data structures are inherently lossy when used with modern Terraform’s type system, and so that is why we implemented a new library without those legacy restrictions.

Thanks for the help. I am testing out the new terraform-plugin-framework but I’m not really finding much help on creating complex schemas.

For some reason, when I build my schema below, all my sub properties in the Object are required. I cannot seem to set them as optional.

Does anybody know how I can set them as optional?

func (r *ExampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
“test_root”: {
Optional: true,
Type: types.ObjectType{
AttrTypes: map[string]attr.Type{
“val1”: types.StringType,
“val2”: types.StringType,
},
},
},
},
}, nil
}

Hi @cwsunderland :wave:

In case you haven’t already discovered this, terraform-plugin-framework implements support for Nested Attributes (for future readers, this documentation may have already moved to the “Attributes” page), which enables full schema definitions for underlying attributes. Object attributes in Terraform only allow defining type information.

To enable this sort of Terraform configuration:

resource "examplecloud_example" "example" {
  test_root = {
     val1 = "set"
     # val2 not set
  }
}

In terraform-plugin-framework v1.0.0 this is the equivalent single nested attribute definition:

func (r *ExampleResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
  resp.Schema = schema.Schema{
    Attributes: map[string]schema.Attribute{
      "test_root": schema.SingleNestedAttribute{
        Attributes: map[string]schema.Attribute{
          "val1": schema.StringAttribute{
            Optional: true,
          },
          "val2": schema.StringAttribute{
            Optional: true,
          },
        },
        Optional: true,
      },
    },
  }
}

If the attribute should be a list, map, or set of objects, there are similar ListNestedAttribute, MapNestedAttribute, and SetNestedAttribute schema types available.

Technically, it is also possible to implement this sort of schema using Blocks, however that is only recommended for migrating existing terraform-plugin-sdk schemas. Nested attributes are preferred because they are easier for practitioners to configure using expressions, instead of needing special dynamic block syntax.