Skip to content

Commit

Permalink
Updates following code review (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
bendbennett committed Jul 28, 2022
1 parent 6cfe57f commit 489ea99
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .changelog/426.txt
@@ -0,0 +1,3 @@
```release-note:bug
internal/fwserver: Ensured `UpgradeResourceState` calls from Terraform 0.12 properly ignored attributes not defined in the schema
```
19 changes: 12 additions & 7 deletions internal/fwserver/server_upgraderesourcestate.go
Expand Up @@ -44,6 +44,15 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS
return
}

// Define options to be used when unmarshalling raw state.
// IgnoreUndefinedAttributes will silently skip over fields in the JSON
// that do not have a matching entry in the schema.
unmarshalOpts := tfprotov6.UnmarshalOpts{
ValueFromJSONOpts: tftypes.ValueFromJSONOpts{
IgnoreUndefinedAttributes: true,
},
}

// Terraform CLI can call UpgradeResourceState even if the stored state
// version matches the current schema. Presumably this is to account for
// the previous terraform-plugin-sdk implementation, which handled some
Expand All @@ -54,7 +63,7 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS
// detail for provider developers. Instead, the framework will attempt to
// roundtrip the prior RawState to a State matching the current Schema.
//
// TODO: To prevent provider developers from accidentially implementing
// TODO: To prevent provider developers from accidentally implementing
// ResourceWithUpgradeState with a version matching the current schema
// version which would never get called, the framework can introduce a
// unit test helper.
Expand All @@ -67,11 +76,7 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS

resourceSchemaType := req.ResourceSchema.TerraformType(ctx)

rawStateValue, err := req.RawState.UnmarshalWithOpts(resourceSchemaType, tftypes.UnmarshalOpts{
JSONOpts: tftypes.JSONOpts{
IgnoreUndefinedAttributes: true,
},
})
rawStateValue, err := req.RawState.UnmarshalWithOpts(resourceSchemaType, unmarshalOpts)

if err != nil {
resp.Diagnostics.AddError(
Expand Down Expand Up @@ -147,7 +152,7 @@ func (s *Server) UpgradeResourceState(ctx context.Context, req *UpgradeResourceS

priorSchemaType := resourceStateUpgrader.PriorSchema.TerraformType(ctx)

rawStateValue, err := req.RawState.Unmarshal(priorSchemaType)
rawStateValue, err := req.RawState.UnmarshalWithOpts(priorSchemaType, unmarshalOpts)

if err != nil {
resp.Diagnostics.AddError(
Expand Down
85 changes: 85 additions & 0 deletions internal/fwserver/server_upgraderesourcestate_test.go
Expand Up @@ -466,6 +466,91 @@ func TestServerUpgradeResourceState(t *testing.T) {
},
},
},
"PriorSchema-and-State-json-mismatch": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.UpgradeResourceStateRequest{
RawState: testNewRawState(t, map[string]interface{}{
"id": "test-id-value",
"required_attribute": true,
"nonexistent_attribute": "value",
}),
ResourceSchema: schema,
ResourceType: &testprovider.ResourceType{
GetSchemaMethod: func(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
return schema, nil
},
NewResourceMethod: func(_ context.Context, _ tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
return &testprovider.ResourceWithUpgradeState{
Resource: &testprovider.Resource{},
UpgradeStateMethod: func(ctx context.Context) map[int64]tfsdk.ResourceStateUpgrader {
return map[int64]tfsdk.ResourceStateUpgrader{
0: {
PriorSchema: &tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"id": {
Type: types.StringType,
Computed: true,
},
"optional_attribute": {
Type: types.BoolType,
Optional: true,
},
"required_attribute": {
Type: types.BoolType,
Required: true,
},
},
},
StateUpgrader: func(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) {
var priorStateData struct {
Id string `tfsdk:"id"`
OptionalAttribute *bool `tfsdk:"optional_attribute"`
RequiredAttribute bool `tfsdk:"required_attribute"`
}

resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)

if resp.Diagnostics.HasError() {
return
}

upgradedStateData := struct {
Id string `tfsdk:"id"`
OptionalAttribute *string `tfsdk:"optional_attribute"`
RequiredAttribute string `tfsdk:"required_attribute"`
}{
Id: priorStateData.Id,
RequiredAttribute: fmt.Sprintf("%t", priorStateData.RequiredAttribute),
}

if priorStateData.OptionalAttribute != nil {
v := fmt.Sprintf("%t", *priorStateData.OptionalAttribute)
upgradedStateData.OptionalAttribute = &v
}

resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateData)...)
},
},
}
},
}, nil
},
},
Version: 0,
},
expectedResponse: &fwserver.UpgradeResourceStateResponse{
UpgradedState: &tfsdk.State{
Raw: tftypes.NewValue(schemaType, map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "test-id-value"),
"optional_attribute": tftypes.NewValue(tftypes.String, nil),
"required_attribute": tftypes.NewValue(tftypes.String, "true"),
}),
Schema: schema,
},
},
},
"UpgradedState-missing": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
Expand Down

0 comments on commit 489ea99

Please sign in to comment.