-
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
helper/schema: Custom diff logic support for providers #14887
Conversation
Some notes on destroy logic:
|
This is now ready for review. What I've done is just removed the unused This leaves 2 non-blocking outstanding questions:
Otherwise, this is good to go! |
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.
Awesome work, @vancluever!
I left various questions and comments inline, but it's generally just me trying to make sure I understand correctly what's going on here. I apologize that I didn't get a chance yet to actually run the code, so my questions so far are based just on reading it.
r.e. your thoughts about deposed resources, I would've expected that we could ignore that problem since we don't allow customizing of destroy diffs anyway. Is there a subtlety there that I missed?
I think it'd be helpful to pick out at least one of the use-cases from the original proposal and see how it looks in practice with this new API in place... that would help to make this feel more real and get to grips with how this behaves. No worries if you don't have time to do that... it's probably a good exercise for me to try to implement one anyway, to cement my understanding of how this is all fitting together.
Thanks for working on this! It's looking really promising, and I'm pleasantly surprised to see how little this disturbed the existing helper/schema
logic, which has a history of being a bit "finicky" when changed.
} | ||
// Note that this gets put into state after the update, regardless of whether | ||
// or not anything is acted upon in the diff. | ||
d.SetNew("computed", d.Get("computed").(int)+1) |
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.
Are there any ill-effects if the Create
/ Update
implementations override this to be a different value before they return? If the final write "wins" then that seems fine, but I want to make sure we don't end up creating the dreaded "Diffs didn't match during apply" error here in that case.
At the very least, we'd need to document that providers shouldn't do that if so, but ideally we'd find a way to make it not arise in the first place since those errors tend to be pretty annoying to track down when the do arise in the wild.
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.
From what I understand, I don't think there should be any ill effects. What would happen is any values that got set in with Set*
functions in the schema.ResourceData
that gets passed along would be set in the set
writer layer, while the data set in the diff function would have been already properly rolled into the diff and would exist exclusively in the diff
reader layer. set
takes precedence, so what I'm pretty sure will happen is that after Create
or Update
returns any value that has been put into the set
writer will be the one that "wins" when the new state is calculated.
The final step of a Resource.Apply
is to merge the state using ResourceData.State()
, which reconciles the state with all the data collected in the various levels of the writer, starting with the set
layer (or state
if in partial mode without a SetPartial
for the specific key, which bypasses the diff anyway, before this change), and returns it, which ultimately becomes the new state.
The good old "diffs didn't match during apply" error was something I was worried about too, but I don't think we are at risk for that, mainly because EvalCompareDiff
gets evaluated before EvalApply
, which means that 1) state hasn't been written yet, and 2) Create
or Update
will not have fired anyway, meaning that there is no risk in either of these creating an issue. What I think we need to be more worried about is a pre-written plan drifting too far from the current real state of the resource that any custom diff behaviour is pulling to calculate the diff. However, I think that's an accurate case of what you were describing in #8769 where the user may need to re-refresh (and then re-plan, ultimately).
I think this could be properly vetoed - the drift could be detected via checking the current real world value and then comparing it to the data from GetChange
, giving the opportunity to provide a friendly error to the user to request they re-plan.
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'm kind of wondering if we can possibly build some safety in here so that this can be guarded against before it hits EvalCompareDiff
and spews both of the diffs in a way that's not so friendly to the user. I want to come back to this.
return &schema.Resource{ | ||
Create: testResourceCustomDiffCreate, | ||
Read: testResourceCustomDiffRead, | ||
CustomizeDiff: testResourceCustomDiffCustomizeDiff, |
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.
Minor cosmetic nit: visually most of the existing Resource
attributes are short, so they line up nicely when formatted in a struct literal like this. Perhaps we could preserve that here by just calling this Diff
. I think that's consistent with the other usage here, since all of these operations are in some sense "customizing" the default behavior that helper/schema
naturally does.
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.
Makes sense - I kind of was wondering this myself and I guess I didn't want a developer thinking that Terraform wasn't doing any diffing behaviour if this was left out now, but I do like the more concise Diff
for sure. Will adjust!
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.
Hmm, so it turns out that this can't necessarily function because it collides with the already existing resource.Diff
, of course... doh!
Trying to think of something else that still keeps the spirit of what we are trying to do 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.
How about Review
or Revise
? That is kind of what is happening here... the diff is being reviewed, and possibly being vetoed or rejected. :)
Other names I could think of were either too close to CRUD-like things that already exist (like Patch
) or just didn't really roll off the tongue like the current set of functions.
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.
Ahh yes, of course. I forgot that there are actually methods on this struct. 😀
I think part of why this is hard to name is that this function kinda serves two purposes:
- Check whether a diff is valid
- Alter a diff to provide more context
So AlterDiff
seems promising if we consider the checking to be an implicit side-effect, but that's still longer than all of the other verbs here, so doesn't really make a great deal of difference.
So with all of that said, maybe let's just stick with CustomizeDiff
and accept the weird visual texture it creates... too minor a detail to have sweated this much over it. 😀
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.
FWIW, I did go forward with changing it to Review
last night - check 6cbfe22 for the commit and how it looks cosmetically. I can roll it back to CustomizeDiff
if you still think it's too ambiguous.
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 don't want to bike-shed it too much, but to me "reviewing" a diff sounds like something a human would do rather than something code would do, and feels a little vague.
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.
No sweat :) will roll this back. I think after this the only thing left to review will be the deposed stuff.
helper/schema/resource_diff.go
Outdated
} | ||
|
||
if computed { | ||
w.computedKeys[strings.Join(address, ".")] = 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.
Is there a legitimate reason to pass both a non-nil value
and computed = true
at the same time? If not, I'd suggest that we catch that and return an error, since I think otherwise we might produce diffs that violate assumptions made elsewhere in Terraform.
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.
Hmm, I was running under the assumption that a zero value could still be legitimately zero and not computed, probably a mistake I made by looking at what NewComputed
diffs look like. Will fix for sure - we will also need to adjust SetNewComputed
so that it doesn't try and pull a zero value for the key in the schema, and just sets a nil value. Does that sound right?
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.
To be entirely honest I'm not sure what the necessary behavior is here... eventually this all gets flattened to strings in the current diff format, so I would guess that we'd want to set this to something that eventually becomes the empty string, but I might be wrong about that. If you're seeing Terraform currently generating diffs where e.g. computed ints appear as "0"
in the diff then what you did here is probably the right behavior, even though it feels a little counter-intuitive.
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 think you are right about this - the more I think about the int diffs specifically I don't think I saw a diff of NewComputed
where the value was "0"
. I probably just tunnel visioned on the string case.
helper/schema/resource_diff.go
Outdated
w.lock.Lock() | ||
defer w.lock.Unlock() | ||
if w.computedKeys == nil { | ||
w.computedKeys = make(map[string]bool) |
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.
Maybe it's worth having an init
method which can deal with this initialization task... that way it will be easier to keep the different initialization paths in sync if this evolves in future.
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.
Can do! Looks like a pattern kind of used in ResourceData
already, so I'll just follow that general example of gating it behind sync.Once
.
helper/schema/resource_diff.go
Outdated
// | ||
// The object functions similar to ResourceData, however most notably lacks | ||
// Set, SetPartial, and Partial, as it should be used to change diff values | ||
// only. Most other frist-class ResourceData functions exist, namely Get, |
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.
frist
😀
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.
Turns spell checking on for source files in vim ;)
helper/schema/resource_diff.go
Outdated
return fmt.Errorf("SetNew, SetNewComputed, and SetDiff are allowed on computed attributes only - %s is not one", key) | ||
} | ||
|
||
return d.setDiff(key, old, new, computed) |
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'm curious to know what Terraform's behavior is if you set old
to a "lying" value that doesn't match the current state. That feels like it would violate some invariants expected by Terraform core. It might be "safer" to not allow that and instead to expect that Read
should get the "old" value into a consistent state first, and then this diff API can then worry only about new values.
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'm fine with that :) it makes sense to completely offload old value calculation to refresh/read for sure. If we do this too, we can get rid of the old value writer layer as well and just use state for old values.
Hey @apparentlymart! Answered the comments! And finally, with regard to deposed resources - the main issue is that diff logic in I haven't gone down the rabbit hole enough to fully understand how a resource is deposed and ultimately how it moves to the Will apply the other changes when I can! |
Alright, I think we have touched all of the specific points of the review now, aside from the issue on deposed resources. Let me know your thoughts on that! EDIT: Also want to look into making |
helper/schema/resource_diff.go
Outdated
if !d.schema[key].Computed { | ||
return fmt.Errorf("SetNew only operates on computed keys - %s is not one", key) | ||
} | ||
return d.setDiff(key, d.get(strings.Split(key, "."), "state").Value, value, false) |
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.
During some testing in which I was prototyping using these features in the Nomad provider, I noticed that if you call SetNew
with a key that doesn't exist in the schema you can get a panic:
2017/05/31 15:36:04 [DEBUG] plugin: terraform: [signal SIGSEGV: segmentation violation code=0x1 addr=0x50 pc=0xace9b1]
2017/05/31 15:36:04 [DEBUG] plugin: terraform:
2017/05/31 15:36:04 [DEBUG] plugin: terraform: goroutine 72 [running]:
2017/05/31 15:36:04 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/helper/schema.(*ResourceDiff).SetNew(0xc4203f6940, 0x51fdf73, 0x2, 0x44cba80, 0xc4202a13f0, 0x0, 0x7c67d80)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /home/mart/Devel/terraform/src/github.com/hashicorp/terraform/helper/schema/resource_diff.go:259 +0x71
2017/05/31 15:36:04 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/builtin/providers/nomad.resourceJobCustomizeDiff(0xc4203f6940, 0x50303a0, 0xc42059d480, 0xc4200c5300, 0xc4203f6940)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /home/mart/Devel/terraform/src/github.com/hashicorp/terraform/builtin/providers/nomad/resource_job.go:77 +0x156
2017/05/31 15:36:04 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/helper/schema.schemaMap.Diff(0xc42055aba0, 0xc4201e0f00, 0xc420535f50, 0x5399a80, 0x50303a0, 0xc42059d480, 0x1, 0x18, 0x28)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /home/mart/Devel/terraform/src/github.com/hashicorp/terraform/helper/schema/schema.go:413 +0xb9d
2017/05/31 15:36:04 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/helper/schema.(*Resource).Diff(0xc420329420, 0xc4201e0f00, 0xc420535f50, 0x50303a0, 0xc42059d480, 0x1, 0x28, 0xc420418030)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /home/mart/Devel/terraform/src/github.com/hashicorp/terraform/helper/schema/resource.go:225 +0x187
2017/05/31 15:36:04 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/helper/schema.(*Provider).Diff(0xc420329490, 0xc4201e08c0, 0xc4201e0f00, 0xc420535f50, 0x7f75821c1e10, 0x0, 0x0)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /home/mart/Devel/terraform/src/github.com/hashicorp/terraform/helper/schema/provider.go:255 +0x9b
2017/05/31 15:36:04 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/plugin.(*ResourceProviderServer).Diff(0xc42015a980, 0xc42026b840, 0xc420429600, 0x0, 0x0)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /home/mart/Devel/terraform/src/github.com/hashicorp/terraform/plugin/resource_provider.go:499 +0x57
2017/05/31 15:36:04 [DEBUG] plugin: terraform: reflect.Value.call(0xc420362720, 0xc420434240, 0x13, 0x51ff043, 0x4, 0xc4205e7f20, 0x3, 0x3, 0x53a3bc8, 0xc4200286e8, ...)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /usr/lib/go-1.8/src/reflect/value.go:434 +0x91f
2017/05/31 15:36:04 [DEBUG] plugin: terraform: reflect.Value.Call(0xc420362720, 0xc420434240, 0x13, 0xc420028720, 0x3, 0x3, 0xc42002870c, 0x180001, 0x300000000)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /usr/lib/go-1.8/src/reflect/value.go:302 +0xa4
2017/05/31 15:36:04 [DEBUG] plugin: terraform: net/rpc.(*service).call(0xc4201c13c0, 0xc4201c1380, 0xc420428e40, 0xc420576600, 0xc42026b060, 0x43484c0, 0xc42026b840, 0x16, 0x4348500, 0xc420429600, ...)
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /usr/lib/go-1.8/src/net/rpc/server.go:387 +0x144
2017/05/31 15:36:04 [DEBUG] plugin: terraform: created by net/rpc.(*Server).ServeCodec
2017/05/31 15:36:04 [DEBUG] plugin: terraform: /usr/lib/go-1.8/src/net/rpc/server.go:481 +0x404
2017/05/31 15:36:04 [DEBUG] plugin: /home/mart/Devel/terraform/bin/terraform: plugin process exited
The panic went away once I realized my mistake in naming the attribute, but if possible it'd be nice for this to return an error rather than crash. 😀
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.
Added code to fix this in e61e892.
I built a prototype of how the The work of including Nomad's diff inside Terraform's diff is unfortunately rather arduous due to the way Nomad returns diff information... the API structure is primarily designed for mechanical display in the CLI, rather than to be machine-readable. But it does achieve the goal of allowing Terraform to ensure that the configuration doesn't drift between For a real first version I'd probably tear out all of the |
While I was playing with
|
I can't make heads or tails of that diff at the moment... looks like some sort of weird off-by-one-ish error to me, but I have no idea where that would have gotten so messed up! Only thing I can think of that would be different is the lack of state in the apply diff might be preventing something from happening that is causing the messed up plan diff. Trying to figure out what would cause that, I added a repro test that modifies the list as you would expect it to be modified but haven't come up with anything yet 🤔 |
Still having a hard time re-producing this... even set up a mock-ish repro in the test provider (which I will commit soon) but still nothing... short of setting up a full multi-dc environment in Nomad I'm not too sure how closer I can get. Do you have any |
Although I was changing my nomad job definition to include other datacenters I was actually just running a single instance of nomad agent in dev mode, with |
Gotcha - will give that a spin later then 😀 |
Okay, got the repro going - now to figure out why flagging one attribute as |
One thing I'm kind of thinking of here - possibly the re-diff (with no state)/attribute copy that a |
Figured it out 😀 It's the second run here that performs a new "create" diff on an empty state. Once I dropped the part where we pull the existing
This is behaviour that was inherited from the existing diff logic, with the main purpose from what I can see of making a diff that best reflects a newly created resource. I kind of wonder if this is one of those gotchas that we are going to have to document - that a second pass is made when a new resource is forced, and that providers are going to need to be coded to prepare for that scenario. Thoughts? |
Ahh... so the problem was that the diff was being run twice, and we were re-appending the change a second time? Interesting... Unfortunately Nomad's API doesn't currently give us enough information to produce a full image of the final state without doing these partial updates (it happened to work in this case, I think, just because the diff was to entirely replace the list) but that's a problem with the Nomad provider, not with your code here. Would it be possible for us to arrange for that second call to be made against the original diff, rather than the one that resulted from running the |
Actually, it's not necessarily running off its own output - the second run runs off of the new diff created from the empty state. The workflow goes like this (with All Runs
If
|
Hmm. I'm not sure I follow here. If the second run for It is certainly possible that we could ship a first version that relies on docs for what to do and not to do here, but since the "diffs didn't match" error is often hard to debug if possible I'd like to find a way to at least catch it and show a different error, so we can more easily recognize when we've hit this situation. |
Hmm, this is interesting - added some debug logging to monitor the slice before/after:
So it looks like the issue is with the first diff and not the second... More on this: grepped on a trace-level log (which includes the nomad diff), and no So the second run did return an empty list, and it looks like it was actually the correct diff, on part of the lack of state to pull the I do agree though that the two runs can be confusing to a resource author, I'm just kind of wondering how we can go about mitigating the confusion, seeing as it seems like an important part of the diff when a new resource needs to be forced... maybe we can pass some context down to |
I think we should plan to not include detailed Nomad diff information in the Terraform diff to start, since that requires a lot of complex wrangling of the diff structure. But it seems like there's something underlying this -- something I'm not sure I quite follow -- that indicates a bug or limitation of the Regarding allowing the provider to distinguish between a create and an update, I think that's a reasonable idea. I think we have something similar on the Ideally though I'd prefer to see the |
The reason why the diff doesn't necessarily function the exact same way the second run is because of the lack of state, which - and I'm guessing that the only way we'd know for sure is maybe to page @mitchellh - is because the intention here is to try and make the diff look as close as possible to what the diff for a completely new resource looks like. I would say it's not a bug, but a side-effect of how a diff operates when a resource needs to be replaced, and something that I think should be respected as much as possible and I don't think is really worth it to break, because in that instance, the second run is pretty close to how I think right now, the line is a little too blurred between create and replace to really try and distinguish between the two - the moment that a change is made that ultimately requires a new resource during customization, you need to support the creation case again anyway, so we would probably have to drop support for |
Sorry I think I wasn't clear... I was trying to say that it is expected that the second run in the replace case has no state, and that from the implementer's perspective it should be indistinguishable from the create case, so that the So I think we are agreeing. 😀 |
To keep with the current convention of most other schema.Resource functional fields being fairly short, CustomizeDiff has been changed to "Review". It would be "Diff", however it is already used by existing functions in schema.Provider and schema.Resource.
Meant to remove this before finalizing the PR. :P
When working on this initially, I think I thought that since NewComputed values in the diff were empty strings, that it was using the zero value. After review, it doesn't seem like this is the case - so I have adjusted NewComputed to pass nil values. There is also a guard now that keeps the new value writer from accepting computed fields with non-nil values.
Removed some outdated comments for SetNew, and normalized the computed key note for SetNew, SetNewComputed, and SetDiff.
The consensus is that it's a generally better idea to move setting the functionality of old values completely to the refresh/read process, hence it's moot to have this function around anymore. This also means we don't need the old value reader/writer anymore, which simplifies things.
This could panic if we sent a parent that was actually longer than the child (should almost never come up, but the guard will make it safe anyway). Also fixed a slice style lint warning in UpdatedKeys.
This will make this check safer and will remove the risk of it passing on keys with a similar prefix.
This simplifies the new value initialization and future-proofs it against more complex initialization functionality.
The old comments said that this interface was API compatible with terraform.ResourceProvider's Diff method - with the addition of passing down meta to it, this is no longer the case. Not too sure if this is really a big deal - schema.Resource never fully implemented terraform.ResourceProvider, from what I can see, and the path from Provdier.Diff to Resource.Diff is still pretty clear. Just wanted to remove an outdated comment.
Restoring the naming of this field in the resource back to CustomizeDiff, as this is generally more descriptive of the process that's happening, despite the lengthy name.
Added a list SetNew test to try and reproduce issues testing diff customization with the Nomad provider. We are running into "diffs didn't match during apply", with the plan diff exhibiting a strange off-by-one-type error in a list diff: datacenters.#: "1" => "2" datacenters.0: "dc1" => "dc2" datacenters.1: "" => "dc3" datacenters.2: "" => "dc3" The test here does not reproduce that issue, unfortunately, but should help pinpoint the root cause through elimination.
This fixes nil pointer issues that could come up if an invalid key was referenced (ie: not one in the schema). Also ships a helper validation function to streamline things.
setDiff does not make use of its new parameter anymore, so it has been removed. Also, as there is no more SetDiff (exported) function, mentions of that have been removed from comments too.
A diff new needs to pass basic value checks to be considered the "same". Several provisions have been added to ensure that the list, set, and RequiresNew behaviours that have needed some exceptions in the past are preserved in this new logic. This ensures that we are checking for value equality as much as possible, which will be more important when we transition to the possibility of diffs being sourced from external data.
Needed to add more cases to support value comparison exceptions that the rest of TF expects to work (this fixes tests in various places). Also moved things to a switch block so that it's a little more compact.
Added some more detailed comments to CustomizeDiff's comments. The new comments detail how CustomizeDiff will be called in the event of different scenarios like creating a new resource, diffing an existing one, diffing an existing resource that has a change that requires a new resource, and destroy/tainted resources. Also added similar detail to ForceNew in ResourceDiff. This should help mitigate any confusion that may come up when using CustomizeDiff, especially in the ForceNew scenario when the second run happens with no state.
This keeps CustomizeDiff from being defined on data sources, where it would be useless. We just catch this in InternalValidate like the rest of the CRUD functions that are not used in data sources.
🎉 thanks for the merge @apparentlymart! |
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. |
This is far along enough now that I think putting in a PR is warranted :)
See #8769 for more details!
This adds a new object,
ResourceDiff
, to the schema package. This object, in conjunction with a function defined inCustomizeDiff
in the resource schema, allows for the in-flight customization of a Terraformdiff after the initial diff has been performed.
Use cases for this functionality include:
CustomizeDiff
function, much like the other CRUD functions.As part of this work, many internal diff functions have been moved to use a special
resourceDiffer
interface, to allow for shared functionality betweenResourceDiff
andResourceData
. This may be extended to theDiffSuppressFunc
as well which would restrict use ofResourceData
inDiffSuppressFunc
to generally read-only fields.