-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
deprecation warning for destroy provisioner refs #23559
Conversation
@@ -50,6 +50,11 @@ func decodeProvisionerBlock(block *hcl.Block) (*Provisioner, hcl.Diagnostics) { | |||
} | |||
} | |||
|
|||
// destroy provisioners can only refer to self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny nitpick/question: since we're only giving warnings right now should this comment say destroy provisioners _should_ only refer to self
?
I am completely ok with disagreement :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though that was only there because I backported this from it being an error, I think it might be better as "can". In this case, they "can only refer to self" in order to avoid the deprecation warnings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice clarification, makes sense to me (and I wasn't going to fight you on it anyway)!
Add deprecation warning for references from destroy provisioners or their connections to external resources or values. In order to ensure resource destruction can be completed correctly, destroy nodes must be able to evaluate with only their instance state. We have sufficient information to validate destroy-time provisioners early on during the config loading process. Later on these can be converted to hard errors, and only allow self, count.index, and each.key in destroy provisioners. Limited the provisioner and block evaluation scope later on is tricky, but if the references can never be loaded, then they will never be encountered during evaluation.
8fc94c6
to
8547603
Compare
valid = true | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't there be a panic
on default to catch other RootNames that wouldn't be expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any names not marked as valid
here return the diagnostic below. We don't want to panic in the config loader, because we can instead present a diagnostic to the user to indicate the exact source location that caused the error.
As for a default case, any possible identifier is valid in other contexts, but we want to limit the destroy provisioners to only these 3 cases. I didn't use the default
here, because 2 of the cases have extra conditions that need to be checked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops! Thank you for the explanation.
when = destroy | ||
connection { | ||
host = self.hostname | ||
user = local.user # WARNING: External references from destroy provisioners are deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yay comments!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I missed this feature last time around, but it's a really nice test fixture from @apparentlymart!
The test matches the comment locations and text to the diagnostic output.
We should probably put a pretty prominent note in the release notes, so that people notice and can give feedback on this change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 I feel like doing diag-collapse on this the way we did with interpolation is a good idea if that doesn't automagically happen
diags = append(diags, &hcl.Diagnostic{ | ||
Severity: hcl.DiagWarning, | ||
Summary: "External references from destroy provisioners are deprecated", | ||
Detail: "Destroy time provisioners and their connections may only reference values stored in the instance state, which include 'self', 'count.index', or 'each.key'.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be handy to also include what they had used/referenced?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That will be automatically generated by the Diagnostic.Subject
field when printed out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a subjective thing and I won't mind at all if others disagree, but I think I'd lean towards making this error message a little less implementation-details-y by not mentioning the "instance state" at all.
For example:
Destroy-time provisioners and their connection configurations may only reference
attributes of the related resource, via 'self', 'count.index', or 'each.key'.
For our other deprecation warnings in this cycle we've included an extra paragraph giving a bit of a "why". This one is too hairy to go into all the details, but perhaps as a compromise we could add an additional sentence something like this:
References to other resources during the destroy phase can cause dependency
cycles and interact poorly with create_before_destroy.
As a bonus, a sentence like that might help a user connect the dots between this warning and a subsequent error reporting a graph cycle, giving them a better clue as to how to resolve that error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another idea, though this one is definitely in the "optional polish" category:
Could we perhaps recognize the situation where the problem is in a connection
block that is shared with other provisioners and add an additional hint that it could help to override the shared block with a connection
block directly inside the when = destroy
provisioner
block? From my memory of how this is modeled in configuration I have a feeling it would be easier said than done, so no worries if it seems super hard, but I wanted to raise it just in case there were a relatively straightforward way to hint that here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like that wording better, I'll push an update to that tomorrow.
As for the separate connection blocks, you're right it's a bit hairy because they're loaded separately. Following the path of making config more explicit and easier to understand, I don't think this imposes large burden to move a resource connection into the compatible provisioner when it's encountered, and then the logic of overriding doesn't have to be taken into account be the loader or the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some late-arriving "polish-ish" feedback here. I know this is already merged, and none of this is critical stuff so I'm leaving it here only in case it seems interesting for a follow-up commit.
diags = append(diags, &hcl.Diagnostic{ | ||
Severity: hcl.DiagWarning, | ||
Summary: "External references from destroy provisioners are deprecated", | ||
Detail: "Destroy time provisioners and their connections may only reference values stored in the instance state, which include 'self', 'count.index', or 'each.key'.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a subjective thing and I won't mind at all if others disagree, but I think I'd lean towards making this error message a little less implementation-details-y by not mentioning the "instance state" at all.
For example:
Destroy-time provisioners and their connection configurations may only reference
attributes of the related resource, via 'self', 'count.index', or 'each.key'.
For our other deprecation warnings in this cycle we've included an extra paragraph giving a bit of a "why". This one is too hairy to go into all the details, but perhaps as a compromise we could add an additional sentence something like this:
References to other resources during the destroy phase can cause dependency
cycles and interact poorly with create_before_destroy.
As a bonus, a sentence like that might help a user connect the dots between this warning and a subsequent error reporting a graph cycle, giving them a better clue as to how to resolve that error.
diags = append(diags, &hcl.Diagnostic{ | ||
Severity: hcl.DiagWarning, | ||
Summary: "External references from destroy provisioners are deprecated", | ||
Detail: "Destroy time provisioners and their connections may only reference values stored in the instance state, which include 'self', 'count.index', or 'each.key'.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another idea, though this one is definitely in the "optional polish" category:
Could we perhaps recognize the situation where the problem is in a connection
block that is shared with other provisioners and add an additional hint that it could help to override the shared block with a connection
block directly inside the when = destroy
provisioner
block? From my memory of how this is modeled in configuration I have a feeling it would be easier said than done, so no worries if it seems super hard, but I wanted to raise it just in case there were a relatively straightforward way to hint that here.
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. |
Add deprecation warning for references from destroy provisioners or
their connections to external resources or values. In order to ensure
resource destruction can be completed correctly, destroy nodes must be
able to evaluate with only their instance state.
We have sufficient information to validate destroy-time provisioners
early on during the config loading process. Later on these can be
converted to hard errors, and only allow self, count.index, and each.key
in destroy provisioners. Limiting the provisioner and block evaluation
scope later on is tricky, but if the references can never be loaded,
then they will never be encountered during evaluation.
The new warning output will look like
Background for the upcoming change
Destroy provisioners were added way back in the 0.9.0 release. The initial concept was simple, but the implications of the added dependencies of evaluating provisioners during destroy was not fully thought out. It turns out that it is very easy to inadvertently introduce dependency cycles, yet hard to detect when they might happen, since it requires a certain combination of updates to trigger. For example, the image below shows a simplified graph of 2 resources being replaced, with
a_destroy
containing a provisioner that indirectly references resourceb
. These resource may even be spread across modules, and updated due to unrelated events.In the releases since 0.9.0, these cycles have both become easier to create and much harder to detect and reason about due to the increased use of modules. Modules themselves also lose some of their intended composability, because the inputs and outputs may have unexpected restrictions on which resources can be used with them to ensure there could be no cycles.
Since adding more heuristics and workarounds to detect and break these cycles won't ever be able to solve them all within Terraform's current operating model, we're proposing to deprecate the possibility of evaluating anything but a resource's own state during destroy. This results in a destroy subgraph that will only ever involve dependent resources, and
create_before_destroy
will only need to take into account the edges between the create and destroy nodes (notice in the graph above,create_before_destroy
would remove the cycle, but in other cases it is just as likely to introduce one). In short, this means that any updates involving resource destruction are more reliable and easier to reason about.