-
Notifications
You must be signed in to change notification settings - Fork 43
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
Integrate with PlanResourceChange and ApplyResourceChange #1614
Conversation
ignoreChanges has a bit of a chicken-and-egg problem because of how tfbridge code is written, e.g. Update. diff, err := p.tfc().DiffWithContext(ctx, res.TFName, state, config)
if err != nil {
return nil, errors.Wrapf(err, "diffing %s", urn)
}
if diff == nil {
// It is very possible for us to get here with a nil diff: custom diffing behavior, etc. can cause
// textual/structural changes not to be semantic changes. A better solution would be to change the result of
// Diff to indicate no change, but that is a slightly riskier change that we'd rather not take at the current
// moment.
return &pulumirpc.UpdateResponse{Properties: req.GetOlds()}, nil
}
// Apply any ignoreChanges before we check that the diff doesn't require replacement or deletion since we may be
// ignoring changes to the keys that would result in replacement/deletion.
doIgnoreChanges(ctx, res.TF.Schema(), res.Schema.Fields, olds, news, req.GetIgnoreChanges(), diff)
if diff.Destroy() || diff.RequiresNew() {
return nil, fmt.Errorf("internal: expected diff to not require deletion or replacement"+
" during Update of %s. Found delete=%t, replace=%t. This indicates a bug in provider.",
urn, diff.Destroy(), diff.RequiresNew())
}
if req.Timeout != 0 {
diff.SetTimeout(req.Timeout, shim.TimeoutUpdate)
} Currently PlanResourceChanges computes the terraform.InstanceDiff object AND computes the planned cty.Value that's the result of applying that diff; technically processing ignore changes (or timeouts) should modify BOTH, but this is awkward in the current API. Possibly we can refactor a bit so we push ignoreChanges processor to DiffWithContext. Or else switch this out entirely to use Pulumi-level ignoreChanges like for PF. Tricky. |
I need to check in an additional patch to go-sdk, missed it with go.work. |
pulumi/terraform-plugin-sdk#34 the necessary change. |
This sounds pretty promising to me since it'd allow us to get rid of some of our custom behaviour there. LMK if you need any help to push this through. |
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.
Sanity checking my understanding of the implementation, we introduce a new pkg/tfshim
Provider
called v2Provider2
, which thinks in terms of PlanResourceChange
and ApplyResourceChange
. We also introduce a binary muxer that switches on resource name called providerWithPlanResourceChange
.
I think the overall strategy is good. Do you have a sense of how much work is needed to ship for pulumi/pulumi-aws#2555?
Currently PlanResourceChanges computes the terraform.InstanceDiff object AND computes the planned cty.Value that's the result of applying that diff; technically processing ignore changes (or timeouts) should modify BOTH, but this is awkward in the current API. Possibly we can refactor a bit so we push ignoreChanges processor to DiffWithContext. Or else switch this out entirely to use Pulumi-level ignoreChanges like for PF. Tricky.
I'm not aware of any customer pushback on the Pulumi-level ignoreChanges, so my inclination is to do that. (for simplicity and consistency).
id string, object, meta map[string]interface{}, | ||
) (shim.InstanceState, error) { | ||
if _, gotID := object["id"]; !gotID && id != "" { | ||
copy := map[string]interface{}{} |
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.
Slight performance optimization to pre-allocate space in the map.
copy := map[string]interface{}{} | |
copy := make(map[string]interface{}, len(meta)) |
Optimistically there's 2 days left to work out ignoreChanges and refresh concerns, after which we can enable it in pulumi-aws for this resource only. |
0c38c0c
to
46f4cf1
Compare
Unfortunately, eh, the refresh and import support turn out to be quite coupled to the state representation, so they need to be part of the change. I'm adding some code to do something sensible but it adds to what needs to be tested out. Still on the list - this needs to do state upgrades still and do timeouts correctly. |
d223e57
to
59c21dc
Compare
The intent here is to refactor the code without any observable changes to make possible further extensions such as #1614 . Specifically, before this PR: ```go type Provider interface { ... Diff(t string, s InstanceState, c ResourceConfig) (InstanceDiff, error) } type InstanceDiff interface { ... IgnoreChanges(ignored map[string]bool) } ``` That is the interface is to compute a diff first, and then side-effect to ignore changes. After this PR we have this instead: ```go type Provider interface { ... Diff(t string, s InstanceState, c ResourceConfig, opts DiffOptions) (InstanceDiff, error) } type IgnoreChanges = func() map[string]struct{} type DiffOptions struct { IgnoreChanges IgnoreChanges } ``` So the algorithm to ignore changes is specified upfront when constructing the diff. This seems to be useful to un-constrain the implementation of shim.Provider a little bit, specifically in #1614 that is trying to change the internal representation of InstanceDiff implementation in such a way that it is inconvenient to process IgnoreChanges after the InstanceDiff is constructed already.
That's probably because the user base for PF-based providers is still insignificant. I'm retaining the current ignoreChanges model, we can contemplate a change as a separate work item. |
72f0661
to
57c17db
Compare
3fbf0e7
to
1b7d17b
Compare
57c17db
to
220a70e
Compare
c89e1f4
to
04d0dab
Compare
outs, err := plugin.UnmarshalProperties(createResp.GetProperties(), plugin.MarshalOptions{KeepUnknowns: true}) | ||
outs, err := plugin.UnmarshalProperties(createResp.GetProperties(), plugin.MarshalOptions{ | ||
KeepUnknowns: true, | ||
SkipNulls: 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.
This was interesting, without SkipNulls: true the PlanResourceChange version returns explicit null entries for every property in the schema that's unset.
assert.NoError(t, err) | ||
assert.True(t, resource.PropertyMap{ | ||
"id": resource.NewStringProperty(""), |
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.
The PlanResourceChange version returns an unknown for "id", but that seems like may be OK.
"id": resource.NewStringProperty(""), | ||
"stringPropertyValue": resource.NewStringProperty("foo"), | ||
"setPropertyValues": resource.NewArrayProperty([]resource.PropertyValue{resource.NewStringProperty("foo")}), | ||
}.DeepEquals(outs)) |
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.
Also the PlanResourceChange version returns __meta here with resource timeouts populated. That seems to be a change.
|
||
func TestIgnoreChangesV2WithPlanResourceChange(t *testing.T) { | ||
opt := shimv2.WithPlanResourceChange(func(string) bool { return true }) | ||
testIgnoreChangesV2(t, shimv2.NewProvider(testTFProviderV2, opt)) |
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.
Starting to bring this option into the test matrix to get some coverage..
Testing import was fruitful, some tweaks needed to handle not-found resource. I'll have to look into adding coverage for refresh and import, found and not found. |
8a61ec0
to
6fe2658
Compare
Got most of the provider2 covered, Remaining coverage :
Manual testing: testing 2555 scenario on top of the fixes; also testing import and refresh. |
@@ -1329,7 +1329,7 @@ func (p *Provider) Delete(ctx context.Context, req *pulumirpc.DeleteRequest) (*p | |||
} | |||
|
|||
// Create a new destroy diff. | |||
diff := p.tf.NewDestroyDiff(ctx, string(t), shim.TimeoutOptions{ |
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 looks like a fairly big change? Isn't urn.Type() a pulumi type, while this requires a TF type?
What does this affect?
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.
It's a bug introduced recently in 39a92a4 specifically to unlock this PR and previously unused/untested.
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.
Looks like a refactor to me wrt the t
parameter - 39a92a4#diff-1dee36d7f4752b488fc6d27ff19bd5e68277063de183d75ef29a02b8bf989045R1332
I don't see how that commit introduces a bug there?
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.
The refactor wasn't tested or used. NewDestroyDiff under default options ignores t
. The intent though was to pass the Terraform resource name in that parameter, similarly to other methods on shim.Provider. Not the Pulumi type.
}) | ||
} | ||
|
||
func TestRefresh(t *testing.T) { |
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.
These are the first import and refresh tests, 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.
TestImport tries to emulate what happens during pulumi import
, and TestRefresh during pulumi refresh
. Both map to shim.Provider Read method but it's a bit difficult for me to remember which is which. Having explicit in the tests remind me. This makes sure to test both branches of isRefresh bool
inside Read. Prior to this test, the not found cases were not covered by tests at all.
Motivation: pulumi/pulumi-terraform-bridge#1614 The bridge is trying to integrate deeper with (*GRPCProviderServer) PlanResourceChange so this patch makes it a little easier: expose the constructed InstanceDiff object apply Pulumi-specified ignore changes to the InstanceDiff object before it gets used to compute the plan
Fixes #1505
Requires pulumi/terraform-plugin-sdk#35
sdk-v2 bridge has a new option that changes the implementation of resource lifecycle to go through PlanResourceChange and ApplyResourceChange TF SDKv2 gRPC methods.
Enables fixing: pulumi/pulumi-aws#2555
Known differences: state returned by the new method will include explicit entries
"foo": null
for properties that are known by the schema but not set in the state. This seems to be benign for repeated diffs and refresh applications.