Resources which implement the ResourceWithValidateConfig interface may find their ValidateConfig() method invoked multiple times, possibly with some attribute values unknown in the early invocations.
Does the same apply to ModifyPlan()?
I’m facing a situation where the plan modifier is flagging a resource for replacement because (I believe) at the time the modifier is run, a critical attribute is unknown. That attribute will eventually turn out to be unchanged.
In fact, if I’m reading the plan summary correctly, the whole resource is unchanged. The only attributes indicating modification are those which say (known after apply) … and which should have been a no-op because they turn out to have the same value in the end.
So…
How should I handle these unknowns in the plan modifier?
I’m struggling to reproduce this condition (the real user’s configuration is impossibly complicated) … How can I reliably force a known after apply value for an attribute in a simple test environment?
edit: Okay, I’ve managed #2 by running the value through a terraform_data resource’s input and output attributes.
Framework itself, for a single PlanResourceChange call, will only call your ModifyPlan function once. If you run terraform apply, you’ll observe two PlanResourceChange calls made to your provider, the first producing the Initial Planned State and the second producing the Final Planned State (after the approval and after it’s dependencies have been resolved). So for an apply command, you can expect two calls to ModifyPlan, a plan command would be one call to ModifyPlan.
The Initial Planned State can have unknowns in the configuration, the Final Planned State will always have a fully known configuration (the entire plan itself isn’t fully known, since you likely have computed values that you’ll set during Create/Update (ApplyResourceChange).
How should I handle these unknowns in the plan modifier?
Similar to validation, if you encounter an unknown value that is in the configuration, you should skip modifying the plan. So you should only require replacement when the value is known and satisfies whatever your plan modifier is trying to do.
I’m struggling to reproduce this condition (the real user’s configuration is impossibly complicated) … How can I reliably force a known after apply value for an attribute in a simple test environment?
edit: Okay, I’ve managed #2 by running the value through a terraform_data resource’s input and output attributes.
Yeah that’s exactly what I do if I’m using a later version of Terraform. If you’re not, you can use the null_resource or one of the random provider resources, since they both have an easy way to trigger new unknown values.
Similar to validation, if you encounter an unknown value that is in the configuration, you should skip modifying the plan. So you should only require replacement when the value is known and satisfies whatever your plan modifier is trying to do.
Awesome I’m so relieved to hear this!
But I remain a little confused:
two PlanResourceChange calls made to your provider, the first producing the Initial Planned State and the second producing the Final Planned State (after the approval and after it’s dependencies have been resolved).
I assume that “after the approval” is referring to the interactive “Do you want to perform these actions?” prompt. Do I have that right?
It seems like Terraform, having already presented a plan and received user approval, should already know what’s going to happen. Springing a RequiresReplace at this point seems a little late…
I assume that “after the approval” is referring to the interactive “Do you want to perform these actions?” prompt. Do I have that right?
Yes that’s correct
It seems like Terraform, having already presented a plan and received user approval, should already know what’s going to happen. Springing a RequiresReplace at this point seems a little late…
Apologies, my answer was incomplete for your scenario. Assuming that your resource absolutely needs to be replaced when you finally determine what the final value is (which you cannot definitively get until all dependencies have been applied), you can set it as needing to be replaced during the final plan but Terraform will prompt the user with an error message indicating that the final plan has changed (which can be resolved with a follow-up apply):
│ Error: Provider produced inconsistent final plan
│
│ When expanding the plan for examplecloud_thing.test to include new values learned so far during apply, provider "registry.terraform.io/austinvalle/sandbox" changed the planned action from Update to
│ DeleteThenCreate.
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
The only difference tolerated from the initial plan to the final plan are unknown values becoming known, from that lifecycle link I gave above:
Terraform Core compares the final with the initial planned state, enforcing the following additional constraints along with those listed above:
* Any attribute that had a known value in the Initial Planned State must have an identical value in the Final Planned State.
* Any attribute that had an unknown value in the Initial Planned State may either remain unknown in the second or take on any known value that conforms to the unknown value's type constraint.
If the practitioner then runs another plan/apply (which would now have a known value for your dependencies in prior state), then the initial plan would show the replacement.
So in general, it’d be best to avoid determining replacement during the final plan if possible since that’s considered an error state to Terraform.
Not sure if it’d be helpful in your exact scenario for determining replacement earlier, but there was a proposed enhancement we had for terraform-plugin-framework to understand partially unknown values (like that a value will definitely not be null), which could aid in some of these early plan modifiers. That work has since stalled but I imagine it’ll get merged some day
In fact, you have helped me a lot here over the last year-ish. Thank you, @austin.valle.
I understand the situation much better now.
In the specific case I’m facing (a bug report from a practitioner with some awesomely convoluted HCL), the unknown values are going to wind up with unmodified final values.
If I defer triggering RequiresReplace until all values are known, I might wind up triggering the Provider produced inconsistent final plan error, but it’s probably exceedingly rare that a configuration will satisfy both conditions required to wind up there:
an unknown critical attribute
the critical attribute’s final value represents a change from the previous value
I’m leaning toward deferring RequiresReplace until final plan, like you recommended.
Maybe I’ll add a boolean which allows the practitioner to opt-in to this behavior. I’m wary of writing a “feature” which I know might lead to “This is a bug in the provider…”