-
Notifications
You must be signed in to change notification settings - Fork 94
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
Can't update Computed fields #175
Comments
This fixes #175. Essentially, our behavior did exactly what we thought it did, but didn't combine with Terraform in the ways we thought it would. We need to set all computed attributes that are null in the _config_ to unknown, not all computed attributes that are null in the _plan_ to unknown, because once a computed attribute has a value in state, it will not have a null plan again unless the provider modifies the plan to null explicitly. This means once a computed attribute gets a value, under the existing code, it can't be updated by the provider. As part of this change, I moved when the null->unknown transform happens. It used to happen at the very end, which gave provider developers no opportunity to override the auto-unknown values with values the provider may know at plan time, such as a provider-level default or the combination of other fields, etc. By doing this first, the provider has the ability to understand that the value is unknown and replace it with a known value. I also fixed a bug that would return an error when trying to use Schema.AttributeAtPath with a path that pointed to an element inside a list, map, or set nested attribute. I also fixed plan modification to still run resource-level plan modification even when the plan is null (i.e., the resource is being deleted) so that diagnostics can be returned. This is useful, for example, when a resource _can't_ be deleted on the API, but the provider is going to just remove it from Terraform's state. This allows provider developers to surface a warning at plan time to practitioners about this limitation, letting them know _before_ they apply the change that that is what's going to happen. Finally, some tests had nonsensical inputs to them that Terraform could never produce--a null config that still had planned values. I updated those tests to have more realistic values by removing the plan and removing the RequiresReplace set in the response, as a deleted resource needing to be replaced doesn't super make sense.
For additional context, this also applied elsewhere in the same resource and unrelated to purely timestamp updates. For example, this schema: "items": {
// If Required is true, Terraform will throw error if user
// doesn't specify value
// If Optional is true, user can choose to supply a value
Required: true,
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
"quantity": {
Type: types.NumberType,
Required: true,
},
"coffee": {
Required: true,
Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{
"id": {
Type: types.NumberType,
Required: true,
},
"name": {
Type: types.StringType,
Computed: true,
},
"teaser": {
Type: types.StringType,
Computed: true,
},
"description": {
Type: types.StringType,
Computed: true,
},
"price": {
Type: types.NumberType,
Computed: true,
},
"image": {
Type: types.StringType,
Computed: true,
},
}),
},
}, tfsdk.ListNestedAttributesOptions{}),
}, Was also generating additional errors in the apply output during update: $ terraform apply
...
hashicups_order.edu: Refreshing state... [id=1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# hashicups_order.edu will be updated in-place
~ resource "hashicups_order" "edu" {
id = "1"
~ items = [
~ { },
# (1 unchanged element hidden)
]
# (1 unchanged attribute hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Changes to Outputs:
~ edu_order = {
~ items = [
{
coffee = {
description = ""
id = 3
image = "/nomad.png"
name = "Nomadicano"
price = 150
teaser = "Drink one today and you will want to schedule another"
}
quantity = 2
},
~ {
~ coffee = {
~ id = 1 -> 2
# (5 unchanged elements hidden)
}
# (1 unchanged element hidden)
},
]
# (2 unchanged elements hidden)
}
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
hashicups_order.edu: Modifying... [id=1]
╷
│ Error: Provider produced inconsistent result after apply
│
│ When applying changes to hashicups_order.edu, provider "provider[\"registry.terraform.io/hashicorp/hashicups\"]" produced an unexpected new value: .items: was
│ cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"coffee":cty.ObjectVal(map[string]cty.Value{"description":cty.StringVal(""), "id":cty.NumberIntVal(3),
│ "image":cty.StringVal("/nomad.png"), "name":cty.StringVal("Nomadicano"), "price":cty.NumberIntVal(150), "teaser":cty.StringVal("Drink one today and you will want to
│ schedule another")}), "quantity":cty.NumberIntVal(2)}),
│ cty.ObjectVal(map[string]cty.Value{"coffee":cty.ObjectVal(map[string]cty.Value{"description":cty.StringVal(""), "id":cty.NumberIntVal(2),
│ "image":cty.StringVal("/packer.png"), "name":cty.StringVal("Packer Spiced Latte"), "price":cty.NumberIntVal(350), "teaser":cty.StringVal("Packed with goodness to
│ spice up your images")}), "quantity":cty.NumberIntVal(2)})}), but now null.
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
╵
╷
│ Error: Provider produced inconsistent result after apply
│
│ When applying changes to hashicups_order.edu, provider "provider[\"registry.terraform.io/hashicorp/hashicups\"]" produced an unexpected new value: .last_updated: was
│ cty.StringVal("Friday, 24-Sep-21 10:23:33 EDT"), but now cty.StringVal("Friday, 24-Sep-21 10:24:41 EDT").
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
╵ We spent some time investigating the behavior with the same provider using Terraform Plugin SDK version 2. That version of the framework also has the same errant behavior, but you will notice that no immediate error is thrown: $ terraform apply
...
hashicups_order.edu: Refreshing state... [id=2]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# hashicups_order.edu will be updated in-place
~ resource "hashicups_order" "edu" {
id = "2"
~ items {
# (1 unchanged attribute hidden)
~ coffee {
~ id = 2 -> 1
name = "Vaulatte"
# (3 unchanged attributes hidden)
}
}
# (1 unchanged block hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Changes to Outputs:
~ edu_order = {
~ items = [
{
coffee = [
{
description = ""
id = 3
image = "/nomad.png"
name = "Nomadicano"
price = 150
teaser = "Drink one today and you will want to schedule another"
},
]
quantity = 2
},
~ {
~ coffee = [
~ {
~ id = 2 -> 1
# (5 unchanged elements hidden)
},
]
# (1 unchanged element hidden)
},
]
# (2 unchanged elements hidden)
}
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
hashicups_order.edu: Modifying... [id=2]
hashicups_order.edu: Modifications complete after 0s [id=2]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Outputs:
edu_order = {
"id" = "2"
"items" = tolist([
{
"coffee" = tolist([
{
"description" = ""
"id" = 3
"image" = "/nomad.png"
"name" = "Nomadicano"
"price" = 150
"teaser" = "Drink one today and you will want to schedule another"
},
])
"quantity" = 2
},
{
"coffee" = tolist([
{
"description" = ""
"id" = 1
"image" = "/vault.png"
"name" = "Vaulatte"
"price" = 200
"teaser" = "Nothing gives you a safe and secure feeling like a Vaulatte"
},
])
"quantity" = 3
},
])
"last_updated" = tostring(null)
} Terraform CLI has special rules for the old framework that allow this misbehavior, demoting the issue to a warning log (unless these computed values are referenced elsewhere, then the error is thrown).
This was done because the change would have been too disruptive to the existing Terraform provider ecosystem and since the old framework could not necessarily distinguish between empty (e.g. The error behavior is expected with the new framework, which is no longer treated special. Also worth noting that the old framework did not have any implicit marking of attributes as unknown either, potentially because of this special Terraform CLI handling. The new framework has two default modes to choose between in this case: The first is "do nothing" or keeping the existing behavior, which means these errors are surfaced to provider developers and practitioners unless the provider is meticulously correct in implementation by introducing plan modifiers that set planned values for The second is to always default The better experience for provider developers and practitioners in this new framework appears that it should default to being conservative and choose the second option of marking all |
Created #180 to consider adding |
This fixes #175. Essentially, our behavior did exactly what we thought it did, but didn't combine with Terraform in the ways we thought it would. We need to set all computed attributes that are null in the _config_ to unknown, not all computed attributes that are null in the _plan_ to unknown, because once a computed attribute has a value in state, it will not have a null plan again unless the provider modifies the plan to null explicitly. This means once a computed attribute gets a value, under the existing code, it can't be updated by the provider. As part of this change, I moved when the null->unknown transform happens. It used to happen at the very end, which gave provider developers no opportunity to override the auto-unknown values with values the provider may know at plan time, such as a provider-level default or the combination of other fields, etc. By doing this first, the provider has the ability to understand that the value is unknown and replace it with a known value. I also fixed a bug that would return an error when trying to use Schema.AttributeAtPath with a path that pointed to an element inside a list, map, or set nested attribute. I also fixed plan modification to still run resource-level plan modification even when the plan is null (i.e., the resource is being deleted) so that diagnostics can be returned. This is useful, for example, when a resource _can't_ be deleted on the API, but the provider is going to just remove it from Terraform's state. This allows provider developers to surface a warning at plan time to practitioners about this limitation, letting them know _before_ they apply the change that that is what's going to happen. Finally, some tests had nonsensical inputs to them that Terraform could never produce--a null config that still had planned values. I updated those tests to have more realistic values by removing the plan and removing the RequiresReplace set in the response, as a deleted resource needing to be replaced doesn't super make sense.
* Fix planning. This fixes #175. Essentially, our behavior did exactly what we thought it did, but didn't combine with Terraform in the ways we thought it would. We need to set all computed attributes that are null in the _config_ to unknown, not all computed attributes that are null in the _plan_ to unknown, because once a computed attribute has a value in state, it will not have a null plan again unless the provider modifies the plan to null explicitly. This means once a computed attribute gets a value, under the existing code, it can't be updated by the provider. As part of this change, I moved when the null->unknown transform happens. It used to happen at the very end, which gave provider developers no opportunity to override the auto-unknown values with values the provider may know at plan time, such as a provider-level default or the combination of other fields, etc. By doing this first, the provider has the ability to understand that the value is unknown and replace it with a known value. I also fixed a bug that would return an error when trying to use Schema.AttributeAtPath with a path that pointed to an element inside a list, map, or set nested attribute. I also fixed plan modification to still run resource-level plan modification even when the plan is null (i.e., the resource is being deleted) so that diagnostics can be returned. This is useful, for example, when a resource _can't_ be deleted on the API, but the provider is going to just remove it from Terraform's state. This allows provider developers to surface a warning at plan time to practitioners about this limitation, letting them know _before_ they apply the change that that is what's going to happen. Finally, some tests had nonsensical inputs to them that Terraform could never produce--a null config that still had planned values. I updated those tests to have more realistic values by removing the plan and removing the RequiresReplace set in the response, as a deleted resource needing to be replaced doesn't super make sense. * Revert t.Log() for diagnostics debugging for now It will likely get documented and consistently applied later either as-is or using a custom go-cmp formatter. See also #135. * Revert Schema.AttributeAtPath implicit parent Attribute handling and add unit testing * populate missing config values in tests * test nested unknown value setting * map AttributeType -> ElementType Co-authored-by: Brian Flad <bflad417@gmail.com> Co-authored-by: Katy Moe <katy@katy.moe>
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. |
Module version
Relevant provider source code
Expected Behavior
On update,
last_updated
should reflect the current time.Actual Behavior
Teraform threw an error about inconsistent results after apply:
Steps to Reproduce
Explanation
The root cause of this is that our
markComputedNilsAsUnknown
transform function is just... wrong.The way it behaves today is that any computed attribute in the proposed new state that is null gets set to unknown. Which lets the provider choose a value for it at apply time, as Computed promises. But! A wrinkle. The proposed new state will use the state value, not null, for any computed attribute with a non-null value in state:
This means once these computed attributes get a value in state, we no longer mark them as unknown. We even have a test that confirms that behavior.
However, this is non-obvious behavior and we should instead mark the field as unknown automatically if it's
null
in the config and computed, so that the provider always has the option of changing it whenever Terraform will allow it. We should do this before plan modifiers are run, so providers have the opportunity to override this behavior.The text was updated successfully, but these errors were encountered: