Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Computed properties cannot be updated in Read #809

Closed
timlinquist opened this issue Jul 22, 2023 · 6 comments
Closed

Computed properties cannot be updated in Read #809

timlinquist opened this issue Jul 22, 2023 · 6 comments
Labels
bug Something isn't working

Comments

@timlinquist
Copy link

Based on the documentation it suggests I should update the computed property in the Read method of the resource. However the computer properties do not seem to work this way. I believe a plan modifier is required in order for this to work? It's confusing why this is the case or what the intention is behind computed properties and why they can't be updated in the Read method as that is required to get the plan to detect changes are required.

Module version

go list -m github.com/hashicorp/terraform-plugin-framework/...
github.com/hashicorp/terraform-plugin-framework v1.3.2

Relevant provider source code

// StaticAssetReleaseResourceModel describes the resource data model.
type StaticAssetReleaseResourceModel struct {
	Version types.String `tfsdk:"version"`
	Id      types.String `tfsdk:"id"`
}

func (r *StaticAssetReleaseResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
	resp.TypeName = req.ProviderTypeName + "_release"
}

func (r *StaticAssetReleaseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		MarkdownDescription: "Release object mapped to MCN/version in the static asset registry s3 bucket",

		Attributes: map[string]schema.Attribute{
			"version": schema.StringAttribute{
				MarkdownDescription: "Version of release",
				Optional:            true,
				Computed:            true,
			},
			"id": schema.StringAttribute{
				Optional: true,
				Computed: true,
				PlanModifiers: []planmodifier.String{
					stringplanmodifier.UseStateForUnknown(),
				},
				MarkdownDescription: "Random UUID",
			},
		},
	}
}

func (r *StaticAssetReleaseResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
	// Prevent panic if the provider has not been configured.
	if req.ProviderData == nil {
		return
	}

	client, ok := req.ProviderData.(*s3.S3)

	if !ok {
		resp.Diagnostics.AddError(
			"Unexpected Resource Configure Type",
			fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
		)

		return
	}

	r.client = client
}

....

func (r *StaticAssetReleaseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
	tflog.Debug(ctx, "*****************STARTED READ")
	var data *StaticAssetReleaseResourceModel

	// Initialize model from plan
	resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

	.....

	tflog.Debug(ctx, fmt.Sprintf("kilonova manifest version: %v", kilonovaManifest.Version))
	data.Version = types.StringValue(kilonovaManifest.Version)

	tflog.Debug(ctx, fmt.Sprintf("version: %v", data.Version))
	tflog.Debug(ctx, fmt.Sprintf("state version: %v", resp.State.GetAttribute(ctx, path.Root("version"), data)))

	// Save updated data into Terraform state
	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

Terraform Configuration Files

terraform {
  required_providers {
    s3-static-asset-copy = {
      source = "registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy"
    }
  }
}

provider "s3-static-asset-copy" {}

resource "s3-static-asset-copy_release" "test-version" {}

Debug Output

2023-07-21T00:42:33.516-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: *****************STARTED READ: @caller=/Users/tlinquist/Documents/code/s3-static-asset-copy/internal/provider/static_asset_release_resource.go:234 @module=s3_static_asset_copy tf_resource_type=s3-static-asset-copy_release tf_rpc=ReadResource tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 timestamp=2023-07-21T00:42:33.516-0700
2023-07-21T00:42:34.062-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Downloading kilonova manifest: tf_resource_type=s3-static-asset-copy_release tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 tf_rpc=ReadResource @caller=/Users/tlinquist/Documents/code/s3-static-asset-copy/internal/provider/static_asset_release_resource.go:255 @module=s3_static_asset_copy timestamp=2023-07-21T00:42:34.061-0700
2023-07-21T00:42:34.063-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: kilonova manifest version: 0.3.25: @module=s3_static_asset_copy tf_rpc=ReadResource @caller=/Users/tlinquist/Documents/code/s3-static-asset-copy/internal/provider/static_asset_release_resource.go:267 tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 tf_resource_type=s3-static-asset-copy_release timestamp=2023-07-21T00:42:34.063-0700
2023-07-21T00:42:34.063-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: version: "0.3.25": tf_resource_type=s3-static-asset-copy_release tf_rpc=ReadResource @caller=/Users/tlinquist/Documents/code/s3-static-asset-copy/internal/provider/static_asset_release_resource.go:270 @module=s3_static_asset_copy tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 timestamp=2023-07-21T00:42:34.063-0700
2023-07-21T00:42:34.063-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: state version: [{{tftypes.String<"0.3.24"> provider.StaticAssetReleaseResourceModel cannot reflect tftypes.String into a struct, must be an object} {[version]}}]: @module=s3_static_asset_copy tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 tf_rpc=ReadResource @caller=/Users/tlinquist/Documents/code/s3-static-asset-copy/internal/provider/static_asset_release_resource.go:271 tf_resource_type=s3-static-asset-copy_release timestamp=2023-07-21T00:42:34.063-0700
2023-07-21T00:42:34.064-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Called provider defined Resource Read: tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 @module=sdk.framework tf_resource_type=s3-static-asset-copy_release tf_rpc=ReadResource @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwserver/server_readresource.go:102 timestamp=2023-07-21T00:42:34.064-0700
2023-07-21T00:42:34.064-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Value switched to prior value due to semantic equality logic: @module=sdk.framework tf_attribute_path=id tf_req_id=a3950298-fc7f-35c2-08ef-154415400fe9 tf_rpc=ReadResource @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwschemadata/value_semantic_equality.go:85 tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_resource_type=s3-static-asset-copy_release timestamp=2023-07-21T00:42:34.064-0700
2023-07-21T00:42:34.065-0700 [WARN]  Provider "registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy" produced an unexpected new value for s3-static-asset-copy_release.test-version during refresh.
      - .version: was cty.StringVal("0.3.24"), but now cty.StringVal("0.3.25")
2023-07-21T00:42:34.065-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Calling provider defined Resource Configure: @module=sdk.framework tf_resource_type=s3-static-asset-copy_release tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_req_id=7997bc67-ed8c-a4c9-f02d-a91f6145749e tf_rpc=ValidateResourceConfig @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwserver/server_validateresourceconfig.go:42 timestamp=2023-07-21T00:42:34.065-0700
2023-07-21T00:42:34.065-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Called provider defined Resource Configure: @module=sdk.framework tf_req_id=7997bc67-ed8c-a4c9-f02d-a91f6145749e tf_resource_type=s3-static-asset-copy_release @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwserver/server_validateresourceconfig.go:44 tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_rpc=ValidateResourceConfig timestamp=2023-07-21T00:42:34.065-0700
2023-07-21T00:42:34.066-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Calling provider defined Resource Configure: @module=sdk.framework tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_resource_type=s3-static-asset-copy_release tf_rpc=PlanResourceChange tf_req_id=4f7037e1-5c01-47ea-e1bf-7d04545efa16 @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwserver/server_planresourcechange.go:60 timestamp=2023-07-21T00:42:34.066-0700
2023-07-21T00:42:34.066-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Called provider defined Resource Configure: @module=sdk.framework tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_rpc=PlanResourceChange @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwserver/server_planresourcechange.go:62 tf_req_id=4f7037e1-5c01-47ea-e1bf-7d04545efa16 tf_resource_type=s3-static-asset-copy_release timestamp=2023-07-21T00:42:34.066-0700
2023-07-21T00:42:34.067-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Calling provider defined planmodifier.String: @module=sdk.framework tf_req_id=4f7037e1-5c01-47ea-e1bf-7d04545efa16 tf_resource_type=s3-static-asset-copy_release tf_attribute_path=id tf_provider_addr=registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy tf_rpc=PlanResourceChange @caller=/Users/tlinquist/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.3.2/internal/fwserver/attribute_plan_modification.go:1968 description="Once set, the value of this attribute in state will not change." timestamp=2023-07-21T00:42:34.067-0700
2023-07-21T00:42:34.067-0700 [DEBUG] provider.terraform-provider-s3-static-asset-copy: Called provider defined planmodifier.String: tf_resource_type=s3-static-asset-copy_release 
2023-07-21T00:42:34.069-0700 [DEBUG] provider.stdio: received EOF, stopping recv loop: err="rpc error: code = Unavailable desc = error reading from server: EOF"
2023-07-21T00:42:34.073-0700 [DEBUG] provider: plugin process exited: path=/users/tlinquist/go/bin/terraform-provider-s3-static-asset-copy pid=2317

Expected Behavior

Expected the plan to include an update to the version per this guide https://developer.hashicorp.com/terraform/plugin/framework/resources/read

Actual Behavior

A warning was logged that the value was not expected to be updated and the update on the resource state was ignored

Steps to Reproduce

Create a provider/resource using the terraform plugin framework interfaces
Configure a computed property
Change that computed property in the read method

References

@timlinquist timlinquist added the bug Something isn't working label Jul 22, 2023
@bendbennett
Copy link
Contributor

Hi @timlinquist 👋

I've just tried to reproduce the issue you described with the following provider resource code:

func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"configurable_attribute": schema.StringAttribute{
				Optional:            true,
				Computed:            true,
				MarkdownDescription: "Example configurable attribute",
			},
			"id": schema.StringAttribute{
				Computed:            true,
				MarkdownDescription: "Example identifier",
				PlanModifiers: []planmodifier.String{
					stringplanmodifier.UseStateForUnknown(),
				},
			},
		},
	}
}

type exampleResourceData struct {
	ConfigurableAttribute types.String `tfsdk:"configurable_attribute"`
	Id                    types.String `tfsdk:"id"`
}

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
	}

	data.Id = types.StringValue("example-id")
	data.ConfigurableAttribute = types.StringValue("configurable attribute")

	tflog.Trace(ctx, "created a resource")

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

func (e *exampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
	var data exampleResourceData

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

	if resp.Diagnostics.HasError() {
		return
	}

	data.ConfigurableAttribute = types.StringValue("set in read")

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

When I run terraform apply for the first time I see the following CLI output and terraform.tfstate:

Terraform will perform the following actions:

  # example_resource.example will be created
  + resource "example_resource" "example" {
      + configurable_attribute = (known after apply)
      + id                     = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

example_resource.example: Creating...
example_resource.example: Creation complete after 0s [id=example-id]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
{
  "version": 4,
  "terraform_version": "1.4.6",
  "serial": 1,
  "lineage": "2650d772-c5be-87d9-6eec-e1264ea29fa6",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "example_resource",
      "name": "example",
      "provider": "provider[\"registry.terraform.io/bendbennett/playground\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "configurable_attribute": "configurable attribute",
            "id": "example-id"
          },
          "sensitive_attributes": []
        }
      ]
    }
  ],
  "check_results": null
}

When I execute terraform apply for the second time I see the following CLI output and terraform.tfstate:

example_resource.example: Refreshing state... [id=example-id]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
{
  "version": 4,
  "terraform_version": "1.4.6",
  "serial": 2,
  "lineage": "2650d772-c5be-87d9-6eec-e1264ea29fa6",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "example_resource",
      "name": "example",
      "provider": "provider[\"registry.terraform.io/bendbennett/playground\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "configurable_attribute": "set in read",
            "id": "example-id"
          },
          "sensitive_attributes": []
        }
      ]
    }
  ],
  "check_results": null
}

So I'm unable to reproduce the issue you described. Is there any other provider code or configuration which may not have been included in the example you provided?

@timlinquist
Copy link
Author

timlinquist commented Jul 29, 2023

@bendbennett Hi! Sorry I was out of town. I tested again and I indeed see it updated in the state also. Is this expected? Shouldn't a plan be generated that shows the computed property is going to change then the apply be invoked not just the update occur and terraform state nothing changed? Rather than:

# s3-static-asset-copy_release.test-version:
resource "s3-static-asset-copy_release" "test-version" {
    id      = "12f55816-6fad-4160-9050-197a1149ba9e"
    version = "25"
}
terraform apply
╷
│ Warning: Provider development overrides are in effect
│ 
│ The following provider development overrides are set in the CLI configuration:- registry.iac.commercial1.sfdc.cl/gec-mulesoft/s3-static-asset-copy in /users/tlinquist/go/bin
│ 
│ The behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with
│ published releases.
╵
s3-static-asset-copy_release.test-version: Refreshing state... [id=12f55816-6fad-4160-9050-197a1149ba9e]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
terraform state show s3-static-asset-copy_release.test-version
# s3-static-asset-copy_release.test-version:
resource "s3-static-asset-copy_release" "test-version" {
    id      = "12f55816-6fad-4160-9050-197a1149ba9e"
    version = "44"
}

@bendbennett
Copy link
Contributor

@timlinquist I believe that this is expected behaviour.

If the Read method is mutating the state directly then the PlanResourceChange RPC that occurs after the ReadResource RPC, which calls the Read method on the resource, will not detect any differences between the plan and the state.

However, if a plan modifier or a default, which is a particular form of plan modifier, is set on the attribute, and the attribute is initially configured in the supplied Terraform configuration during creation and then the configuration is subsequently removed, then there will be a difference between the plan and the state and the Terraform CLI will render this difference.

For example:

Initial configuration

 resource "example_resource" "example" {
  configurable_attribute = "set in config"
}

Schema

"configurable_attribute": schema.StringAttribute{
	Optional:            true,
	Computed:            true,
	Default:             stringdefault.StaticString("default configurable attribute"),
},

Initial state

            "configurable_attribute": "set in config",

Updated configuration

resource "example_resource" "example" {
#  configurable_attribute = "set in config"
}

CLI Output

Terraform will perform the following actions:

  # example_resource.example will be updated in-place
  ~ resource "example_resource" "example" {
      ~ configurable_attribute = "set in read" -> "default configurable attribute"
        id                     = "example-id"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Updated state

            "configurable_attribute": "default configurable attribute",

@timlinquist
Copy link
Author

@bendbennett Got it. Can I hook into this https://developer.hashicorp.com/terraform/plugin/framework/internals/rpcs#planresourcechange-rpc then I believe to do what I want with the computed property? If I am understanding this right I can override that method to generate the plan.

Thanks for engaging with me! I can close this issue once you reply again in the even somebody else finds it on Google.

@bendbennett
Copy link
Contributor

@timlinquist you may be able to achieve what you want using Plan Modification as plan modifiers are called during the PlanResourceChange RPC.

If you would like specific guidance for your use-case could I ask you to open a post on the Terraform Providers Discuss forum describing the details of what you are trying to achieve? As we've covered the original issue of whether computed properties can be updated in the Read method I think we can close out this issue and link to Discuss for any further examination of your use-case.

@github-actions
Copy link

github-actions bot commented Sep 3, 2023

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants