Provider plugin framework computed blocks - block count changed from x to y

Hi,

I’m looking to implement a resource using the plugin framework and am experiencing issues with nested objects in the schema. From reading some of the SDK migration docs and similar posts here I looked to move this nested object from the schema’s ‘Attributes’ to ‘Blocks’ but now face the ‘block count changed’ error when the resource’s create handler goes to update this block attribute.

Resource struct

type resourceChannelData struct {
	ARN              types.String `tfsdk:"arn"`
	Name             types.String `tfsdk:"name"`
	ChannelGroupName types.String `tfsdk:"channel_group_name"`
	Description      types.String `tfsdk:"description"`
	InputType        types.String `tfsdk:"input_type"`
	IngestEndpoints fwtypes.SetNestedObjectValueOf[ingestEndpointModel] `tfsdk:"ingest_endpoints"`
	Tags            tftags.Map                                          `tfsdk:"tags"`
	TagsAll         tftags.Map                                          `tfsdk:"tags_all"`
}

type ingestEndpointModel struct {
	Id  types.String `tfsdk:"id"`
	Url types.String `tfsdk:"url"`
}

Schema

{
		Attributes: map[string]schema.Attribute{
			names.AttrARN: framework.ARNAttributeComputedOnly(),
			"channel_group_name": schema.StringAttribute{
				Required: true,
			},
			names.AttrName: schema.StringAttribute{
				Required: true,
			},
			names.AttrDescription: schema.StringAttribute{
				Optional: true,
			},
			"input_type": schema.StringAttribute{
				Optional: true,
				Validators: []validator.String{
					stringvalidator.OneOf(func() []string {
						values := awstypes.InputType("").Values()
						strValues := make([]string, len(values))
						for i, v := range values {
							strValues[i] = string(v)
						}
						return strValues
					}()...),
				},
			},
			names.AttrTags:    tftags.TagsAttribute(),
			names.AttrTagsAll: tftags.TagsAttributeComputedOnly(),
		},
		Blocks: map[string]schema.Block{
			"ingest_endpoints": schema.SetNestedBlock{
				CustomType: fwtypes.NewSetNestedObjectTypeOf[ingestEndpointModel](ctx),
				PlanModifiers: []planmodifier.Set{
					setplanmodifier.UseStateForUnknown(),
				},
				Validators: []validator.Set{
					setvalidator.SizeAtMost(1),
				},
				NestedObject: schema.NestedBlockObject{
					Attributes: map[string]schema.Attribute{
						"id": schema.StringAttribute{
							Computed: true,
						},
						"url": schema.StringAttribute{
							Computed: true,
						},
					},
				},
			},
		},
	}

This ‘ingest_endpoints’ property is intended to not be surfaced to the user but instead to be computed from create handler. It will always be set from handler and cannot change. I have tried this functionality both as a ‘Set’ and ‘List’, different permutations of Computed, Required, Validators & PlanModifiers but i consistency receive the Error

Error: Provider produced inconsistent result after apply

When applying changes to aws_media_packagev2_channel.channel, provider
"provider[\"registry.terraform.io/hashicorp/aws\"]" produced an unexpected
new value: .ingest_endpoints: block set length changed from 0 to 2.

This is a bug in the provider, which should be reported in the provider's own

Hey there @mattyboy84 :wave:, welcome to the discuss board and sorry you’re running into trouble here.

So blocks are configuration-only, so the block itself can’t be Computed. They either exist in config or they don’t exist in config. What you’re looking for is a nested attribute, which can be marked as computed: Plugin Development - Framework: List Nested Attribute | Terraform | HashiCorp Developer . SDKv2 works with some legacy type system rules that allowed blocks to fake being computed, so if you’re migrating an existing resource that might make it tricky.

It looks like you might be working in the AWS provider, which I know is using protocol v5 still, which can’t use nested attributes unfortunately. (v6 only)


The maintainers of the AWS provider could maybe give you some input on if they’d prefer to wait for v6/nested attributes to migrate this resource. However, you could also use a normal ListAttribute with an ObjectType element, since all of the fields here seem to be computed? Seen here in this documentation: Computed Blocks: Migrating from SDKv2 to the Framework | Terraform | HashiCorp Developer

Hey Ausin, after some more troubleshooting i got it working. When I first tried adding this field to the top-level ‘Attributes’ I got the “protocol version 5 cannot have Attributes set” Error which led me to test blocks however I did eventually get the schema Attributes configuration correct for the resource :grinning:

My schema eventually became

{
		Attributes: map[string]schema.Attribute{
			names.AttrARN: framework.ARNAttributeComputedOnly(),
			"channel_group_name": schema.StringAttribute{
				Required: true,
			},
			names.AttrName: schema.StringAttribute{
				Required: true,
			},
			names.AttrDescription: schema.StringAttribute{
				Optional: true,
			},
			"input_type": schema.StringAttribute{
				Optional: true,
				Computed: true,
				Validators: []validator.String{
					stringvalidator.OneOf(func() []string {
						values := awstypes.InputType("").Values()
						strValues := make([]string, len(values))
						for i, v := range values {
							strValues[i] = string(v)
						}
						return strValues
					}()...),
				},
				Default: stringdefault.StaticString(string(awstypes.InputTypeHls)),
			},
			"ingest_endpoints": schema.ListAttribute{
				CustomType:  fwtypes.NewListNestedObjectTypeOf[ingestEndpointModel](ctx),
				ElementType: fwtypes.NewObjectTypeOf[ingestEndpointModel](ctx),
				Computed:    true,
				PlanModifiers: []planmodifier.List{
					listplanmodifier.UseStateForUnknown(),
				},
			},
			names.AttrTags:    tftags.TagsAttribute(),
			names.AttrTagsAll: tftags.TagsAttributeComputedOnly(),
		},
	}

Using the ListAttribute for the ingest_endpoints computed array of url endpoints.

And my struct updated to match the new List attribute

type resourceChannelData struct {
	ARN                       types.String                                            `tfsdk:"arn"`
	Name                      types.String                                            `tfsdk:"name"`
	ChannelGroupName          types.String                                            `tfsdk:"channel_group_name"`
	Description               types.String                                            `tfsdk:"description"`
	InputType                 types.String                                            `tfsdk:"input_type"`
	IngestEndpoints           fwtypes.ListNestedObjectValueOf[ingestEndpointModel]    `tfsdk:"ingest_endpoints"`
	Tags                      tftags.Map                                              `tfsdk:"tags"`
	TagsAll                   tftags.Map                                              `tfsdk:"tags_all"`
}

type ingestEndpointModel struct {
	Id  types.String `tfsdk:"id"`
	Url types.String `tfsdk:"url"`
}
1 Like

Awesome! Glad it worked and thanks for reporting back :+1: