-
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
Attribute plan modification #102
Conversation
ab60b8c
to
a1d7d0f
Compare
a1d7d0f
to
b44c515
Compare
This matches my expectations. I'm not sure how we'd let them all operate on the request; what if two or more modified the same field in different ways? Which result would we use?
This also matches my expectations, and my mental model of how plans works tells me it doesn't matter. Unlike applies, plans shouldn't get persisted in the face of an error, I believe, so if an error diagnostic is being returned, I think the plan is just going to get discarded, anyways. So this seems like reasonable behavior that won't have any external negative impacts, as far as I know. |
Some complexity comes with nested attributes - currently it's possible to define Schema{
Attributes: map[string]Attribute{
"disks": {
Optional: true,
Computed: true,
PlanModifiers: ..., // modifies "disks"
Attributes: ListNestedAttributes(map[string]Attribute{
"name": {
Required: true,
Type: types.StringType,
PlanModifiers: ..., // modifies "disks[0].name" etc
},
"size_gb": {
Required: true,
Type: types.NumberType,
},
"boot": {
Required: true,
Type: types.BoolType,
},
}, ListNestedAttributesOptions{}),
},
},
} Currently, the top-level PlanModifiers are run first, followed by any modifiers on nested attributes. This at least needs to be noted in the GoDoc and a test case added, so I'll do that. If we like we could also prohibit PlanModifiers on Attributes without a Type, but I don't see a need to do this as long as the order is unambiguous and clearly documented. |
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.
A few comments, mostly about some testing stuff and about recursion.
tfsdk/serve_test.go
Outdated
@@ -1940,7 +2269,9 @@ func TestServerPlanResourceChange(t *testing.T) { | |||
t.Errorf("Expected planned private to be %q, got %q", tc.expectedPlannedPrivate, got.PlannedPrivate) | |||
return | |||
} | |||
if diff := cmp.Diff(got.RequiresReplace, tc.expectedRequiresReplace, cmpopts.EquateEmpty()); diff != "" { | |||
if diff := cmp.Diff(got.RequiresReplace, tc.expectedRequiresReplace, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(x, y *tftypes.AttributePath) 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.
Hmm, rather than new modifiers on cmp.Diff, does implementing an Equal
method allow us to make this work without the cmpopts?
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, that would work. We could do one of the following:
- Define a
type RequiresReplacePaths []*tftypes.AttributePath
in the framework with anEqual
method that ignores ordering - Do something similar but in plugin-go/tftypes itself.
However, I think it might be better just to have the framework server sort them so the response is deterministic, rather than ignoring the change of order during testing. Added 583f552 and a8991d7 for this purpose.
Also, some merge conflicts to resolve, I believe. |
Yeah, sorry, I think we're getting at the same thing here. I was trying to say I don't know how it would work if we didn't take that approach.
Yeah, I'm fine with just "top-level goes first, nested ones follow", that seems like a really reasonable approach and I think the complexity is worth the extra power. |
Would this feature allow a provider to specify defaults for optional fields? |
@appilon Yes, thanks for raising this. Adding a test case to show this working as expected. A helper func like this makes sense I think: Schema{
Attributes: map[string]Attribute{
"size": {
Optional: true,
PlanModifiers: []AttributePlanModifier{AttributeDefault(val)},
},
},
} where we have either Will follow up with a further PR for any such helpers. Also considering putting |
b44c515
to
84ae041
Compare
Implementation of full recursion of nested attributes works, but the tests are currently running into #112 as |
+1 waiting for this PR merged - I have the same issue as mentioned in #112 |
8763850
to
dc0765b
Compare
In fact I think this doesn't require a plugin-go change (see comment on #112). Changes since review:
Ready for review. |
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! This looks great to me.
// RequiresReplace returns an AttributePlanModifier specifying the attribute as | ||
// requiring replacement. This behaviour is identical to the ForceNew behaviour | ||
// in terraform-plugin-sdk. | ||
func RequiresReplace() AttributePlanModifier { |
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.
nitpick: if we're going to have a constructor, I feel like the underlying type could be unexported to limit our compatibility surface. If we're going to export the underlying type, I don't know that we need a constructor. 🤔
(This should not block merge, we can iterate later.)
ee4eef6
to
0db56e8
Compare
setAttrDiags := resp.Plan.SetAttribute(ctx, attrPath, nestedAttrResp.AttributePlan) | ||
resp.Diagnostics = append(resp.Diagnostics, setAttrDiags...) | ||
if diagnostics.DiagsHasErrors(setAttrDiags) { | ||
return | ||
} | ||
resp.Diagnostics = nestedAttrResp.Diagnostics |
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 this last line expected? I think its just extraneous since they should point to the same address, but it could be confusing as it looks like it might be removing SetAttribute
diagnostics.
I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions. |
Closes #34
Design doc: #64
Needs #107
This PR implements
schema.Attribute.PlanModifiers
and theRequiresReplace
andRequiresReplaceIf
helpers, as described in the design doc.Some more design points that arose during implementation:
AttributePlanModifiers
are specified, should thereq.AttributePlan
value supplied to each be theresp.AttributePlan
value from the previous modifier? This seems the intuitive behaviour (seechained_modifiers
test for example).RequiresReplace
and the planned state still be populated in the server response? This PR assumes so, and that it is Core's responsibility to decide what to do in this case. This also makes it easier to write deterministic tests, as there is otherwise a race condition between early return on certain errors, and RequiresReplace being populated.