Is it possible to allow partial default values for a list/set when some other values are set in the config?

I have a SetNestedAttribute in my schema that looks like this:

"attrs": schema.SetNestedAttribute{
						Description: "...",
						Required: true
						NestedObject: schema.NestedAttributeObject{
							Attributes: map[string]schema.Attribute{
								"stringattr": schema.StringAttribute{
									Description: "...",
									Required:    true,
								},
								"boolattr": schema.BoolAttribute{
									Description: "...",
									Optional:    true,
								},
							},
						},
					},

The service Iā€™m writing this provider for can return additional defaults for attrs, even when some other attrs are specified. For example, with HCL like this:

attrs = [
            {
                stringattr = "example"
                boolattr = true
            }
        ]

The service can return a list including both that attribute from the config, along with one or more additional attributes generated by the service.

This causes an error in the provider as expected:

ā•·
ā”‚ Error: Provider produced inconsistent result after apply
ā”‚ 
ā”‚ When applying changes to a.b, provider
ā”‚ "provider[\"asdf"]" produced an unexpected new value:
ā”‚ .example.attrs: length changed from 1 to 2.
ā”‚ 
ā”‚ This is a bug in the provider, which should be reported in the provider's own issue tracker.
ā•µ

Is there any way to allow for some of the values in the state for a list to come from the config with some additional computed values? I tried changing the attribute to computed and using a planmodifier to add the expected elements to the list but that causes a different error:

Planning failed. Terraform encountered an error while generating this plan.

ā•·
ā”‚ Error: Provider produced invalid plan
ā”‚ 
ā”‚ Provider "asdf" planned an invalid value for
ā”‚ asdf_example.example.attrs: count in plan (2)
ā”‚ disagrees with count in config (1).
ā”‚ 
ā”‚ This is a bug in the provider, which should be reported in the provider's own issue tracker.
ā•µ
1 Like

Hi @henryrecker :wave:

For SetNestedAttributes the configurability rules are the same as for other attributes.

If you have the attribute marked as optional and computed, then the value must either come from the Terraform configuration, or the value can be set in the provider logic. If there is a value set in the configuration, and you try and modify this, by adding to it as you describe, then Terraform will raise data consistency errors as you have encountered.

One option might be to have an optional attribute for a value set in the configuration and have another computed attribute that merges the value from the configuration with the additional values generated by the service.

The schema could look something like:

func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"set_nested_attribute": schema.SetNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"string_attr": schema.StringAttribute{
							Description: "...",
							Required:    true,
						},
						"bool_attr": schema.BoolAttribute{
							Description: "...",
							Optional:    true,
						},
					},
				},
				Required: true,
			},
			"set_nested_attribute_comp": schema.SetNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"string_attr": schema.StringAttribute{
							Description: "...",
							Required:    true,
						},
						"bool_attr": schema.BoolAttribute{
							Description: "...",
							Optional:    true,
						},
					},
				},
				Computed: true,
			},
		},
	}
}

Generation of the value for set_nested_attribute_comp could then handled in the CRUD methods and/or a plan modifier.

Assuming the following Terraform configuration:

resource "example_resource" "example" {
  set_nested_attribute = [
    {
      string_attr = "example"
      bool_attr = true
    }
  ]
}

An implementation in the Create function could then be something like:


type exampleResourceData struct {
	SetNestedAttribute     types.Set `tfsdk:"set_nested_attribute"`
	SetNestedAttributeComp types.Set `tfsdk:"set_nested_attribute_comp"`
}

type setNestedAttribute struct {
	StringAttr types.String `tfsdk:"string_attr"`
	BoolAttr   types.Bool   `tfsdk:"bool_attr"`
}

func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var data exampleResourceData

	diags := req.Plan.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

	var elems []setNestedAttribute

	diags = data.SetNestedAttribute.ElementsAs(ctx, &elems, false)

	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

    // Assumption is that the additional value(s) are generated by the service
	elems = append(elems, setNestedAttributeCo{
		StringAttr: types.StringValue("computed"),
		BoolAttr:   types.BoolValue(true),
	})

	s, diags := types.SetValueFrom(ctx, data.SetNestedAttributeComp.ElementType(ctx), elems)

	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

	data.SetNestedAttributeComp = s

	diags = resp.State.Set(ctx, &data)

	resp.Diagnostics.Append(diags...)
}