How to convert concrete tftypes.Value to something that implements attr.Value

:wave:t2:

I have the following schema:

		Blocks: map[string]schema.Block{
			"domain": schema.SetNestedBlock{
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"comment": schema.StringAttribute{
							MarkdownDescription: "An optional comment about the domain",
							Optional:            true,
						},
						"id": schema.StringAttribute{
							Computed:            true,
							MarkdownDescription: "Unique identifier used by the provider to determine changes within a nested set type",
						},
						"name": schema.StringAttribute{
							MarkdownDescription: "The domain that this service will respond to. It is important to note that changing this attribute will delete and recreate the resource",
							Required:            true,
						},
					},
				},
			},
		},

In my provider’s Create function the plan data is stored into the state, but I need to update the nested block’s id attribute with a computed value.

Here is where I’m having a problem because I can’t do that without causing a compiler error related to how I’m trying to create a set object that I can assign to the "domain" block I’ve defined.

In my code I range over plan.Domain.Elements() and make associated calls to my API to create the resource based on the given plan data, but I need to assign a uuid to each domain before storing it back into the state. So to attempt to do that I’m doing…

	elements := []attr.Value{}

	for _, domainData := range plan.Domain.Elements() {
		// TODO: abstract the conversion code as it's repeated in the Read/Update function.
		v, err := domainData.ToTerraformValue(ctx)
		if err != nil {
			tflog.Trace(ctx, "ToTerraformValue error", map[string]any{"err": err})
			resp.Diagnostics.AddError("ToTerraformValue error", fmt.Sprintf("Unable to convert type to Terraform value: %s", err))
			return
		}

		var attrs map[string]tftypes.Value

		err = v.As(&attrs)
		if err != nil {
			tflog.Trace(ctx, "As error", map[string]any{"err": err})
			resp.Diagnostics.AddError("As error", fmt.Sprintf("Unable to convert type to Go value: %s", err))
			return
		}

		elements = append(elements, types.ObjectValueMust(domain.AttrTypes, attrs))
	}

This doesn’t work because of the compiler error:

cannot use attrs (variable of type map[string]tftypes.Value) as map[string]attr.Value value in argument to types.ObjectValueMust

I then tried to fix this by doing something like:

		m := make(map[string]attr.Value)

    for k, v := range attrs {
      m[k] = v
    }

But I can’t because of the compiler error:

cannot use v (variable of type tftypes.Value) as attr.Value value in assignment: tftypes.Value does not implement attr.Value (wrong type for method Equal)

have Equal(o tftypes.Value) bool
want Equal(attr.Value) bool

So how do I set a computed attribute in a nested set block?

Thanks.

I am currently learning terraform-provider-framework too.

I was curious how this might work, so I tried some things.

It seems that it works if you define a suitable Go type to model your entire resource:

type thingModel struct {
	Domains []struct {
		Name    string       `tfsdk:"name"`
		Comment types.String `tfsdk:"comment"`
		ID      types.String `tfsdk:"id"`
	} `tfsdk:"domain"`
}

and then you can just pull the whole thing into the native Go struct, fill in the fields you need to, and push the whole lot back into the state:

func (t *thingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
	var model thingModel
	diags := req.Plan.Get(ctx, &model)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	for index := range model.Domains {
		domain := &model.Domains[index]
		if domain.ID.IsUnknown() {
			domain.ID = types.StringValue(base62.MustRandom(10))
		}
	}

	diags = resp.State.Set(ctx, &model)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}
}
1 Like

Thanks @maxb this does work! I was of the understanding that I needed to use tftypes.Set and couldn’t use a nested struct. But it indeed works as you have suggested. Thanks!

You can read more about the conversion rules at Plugin Development - Framework: Conversion Rules | Terraform | HashiCorp Developer

1 Like