Skip to content

Commit c2f6536

Browse files
authored
bugfix: unstable response values may cause provider produced inconsistant results (#687)
* bugfix: unstable response values may cause provider produced inconsistant results * add more fields
1 parent ac4a733 commit c2f6536

File tree

8 files changed

+144
-0
lines changed

8 files changed

+144
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v2.2.0 (unreleased)
2+
3+
BUG FIXES:
4+
- Fix a bug that the provider produced inconsistent result after apply when default output feature is enabled.
5+
Notice: Terraform will detect the `output` field's changes made outside of Terraform since the last "terraform apply". You can run `terraform refresh` to update the state file with the latest values.
6+
17
## v2.1.0
28
FEATURES:
39
- `azapi_resource` resource: Support resource move operation, it allows moving resources from `azurerm` provider.

internal/services/azapi_resource.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,7 @@ func (r *AzapiResource) CreateUpdate(ctx context.Context, requestPlan tfsdk.Plan
698698
var defaultOutput interface{}
699699
if !r.ProviderData.Features.DisableDefaultOutput {
700700
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
701+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
701702
}
702703
output, err := buildOutputFromBody(responseBody, plan.ResponseExportValues, defaultOutput)
703704
if err != nil {
@@ -758,6 +759,7 @@ func (r *AzapiResource) CreateUpdate(ctx context.Context, requestPlan tfsdk.Plan
758759
var defaultOutput interface{}
759760
if !r.ProviderData.Features.DisableDefaultOutput {
760761
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
762+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
761763
}
762764
output, err := buildOutputFromBody(responseBody, plan.ResponseExportValues, defaultOutput)
763765
if err != nil {
@@ -898,6 +900,7 @@ func (r *AzapiResource) Read(ctx context.Context, request resource.ReadRequest,
898900
var defaultOutput interface{}
899901
if !r.ProviderData.Features.DisableDefaultOutput {
900902
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
903+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
901904
}
902905
output, err := buildOutputFromBody(responseBody, model.ResponseExportValues, defaultOutput)
903906
if err != nil {
@@ -1032,6 +1035,7 @@ func (r *AzapiResource) ImportState(ctx context.Context, request resource.Import
10321035
var defaultOutput interface{}
10331036
if !r.ProviderData.Features.DisableDefaultOutput {
10341037
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
1038+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
10351039
}
10361040
output, err := buildOutputFromBody(responseBody, state.ResponseExportValues, defaultOutput)
10371041
if err != nil {

internal/services/azapi_resource_data_source.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ func (r *AzapiResourceDataSource) Read(ctx context.Context, request datasource.R
265265
var defaultOutput interface{}
266266
if !r.ProviderData.Features.DisableDefaultOutput {
267267
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
268+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
268269
}
269270
output, err := buildOutputFromBody(responseBody, model.ResponseExportValues, defaultOutput)
270271
if err != nil {

internal/services/azapi_resource_list_data_source.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/Azure/terraform-provider-azapi/internal/retry"
1212
"github.com/Azure/terraform-provider-azapi/internal/services/myvalidator"
1313
"github.com/Azure/terraform-provider-azapi/internal/services/parse"
14+
"github.com/Azure/terraform-provider-azapi/utils"
1415
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1516
"github.com/hashicorp/terraform-plugin-framework/datasource"
1617
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
@@ -159,6 +160,7 @@ func (r *ResourceListDataSource) Read(ctx context.Context, request datasource.Re
159160
var defaultOutput interface{}
160161
if !r.ProviderData.Features.DisableDefaultOutput {
161162
defaultOutput = responseBody
163+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
162164
}
163165
output, err := buildOutputFromBody(responseBody, model.ResponseExportValues, defaultOutput)
164166
if err != nil {

internal/services/azapi_update_resource.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ func (r *AzapiUpdateResource) CreateUpdate(ctx context.Context, plan tfsdk.Plan,
416416
var defaultOutput interface{}
417417
if !r.ProviderData.Features.DisableDefaultOutput {
418418
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
419+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
419420
}
420421
output, err := buildOutputFromBody(responseBody, model.ResponseExportValues, defaultOutput)
421422
if err != nil {
@@ -502,6 +503,7 @@ func (r *AzapiUpdateResource) Read(ctx context.Context, request resource.ReadReq
502503
var defaultOutput interface{}
503504
if !r.ProviderData.Features.DisableDefaultOutput {
504505
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
506+
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
505507
}
506508
output, err := buildOutputFromBody(responseBody, model.ResponseExportValues, defaultOutput)
507509
if err != nil {

internal/services/common.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,24 @@ func buildOutputFromBody(responseBody interface{}, modelResponseExportValues typ
5757
return types.DynamicNull(), errors.New("unsupported type for response_export_values, must be a list or map")
5858
}
5959
}
60+
61+
func volatileFieldList() []string {
62+
return []string{
63+
"etag",
64+
"updatedBy",
65+
"updated",
66+
"updatedOn",
67+
"updatedTimestamp",
68+
"lastUpdatedOn",
69+
"lastUpdated",
70+
"lastUpdatedTime",
71+
"lastUpdatedTimeUtc",
72+
"lastUpdatedDateUTC",
73+
"modifiedOn",
74+
"lastModifiedUtc",
75+
"lastModifiedTimeUtc",
76+
"lastModifiedAt",
77+
"lastModifiedBy",
78+
"lastModifiedByType",
79+
}
80+
}

utils/json.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,31 @@ func NormalizeObject(input interface{}) interface{} {
289289
return output
290290
}
291291

292+
// RemoveFields is used to remove fields from input
293+
func RemoveFields(input interface{}, fields []string) interface{} {
294+
if input == nil {
295+
return input
296+
}
297+
switch v := input.(type) {
298+
case map[string]interface{}:
299+
for _, field := range fields {
300+
delete(v, field)
301+
}
302+
for key, value := range v {
303+
v[key] = RemoveFields(value, fields)
304+
}
305+
return v
306+
case []interface{}:
307+
res := make([]interface{}, 0)
308+
for _, item := range v {
309+
res = append(res, RemoveFields(item, fields))
310+
}
311+
return res
312+
default:
313+
return input
314+
}
315+
}
316+
292317
func isZeroValue(value interface{}) bool {
293318
if value == nil {
294319
return true

utils/json_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,3 +1018,86 @@ func Test_UpdateObjectDuplicateIdentifiersWithInconsistentOrdering(t *testing.T)
10181018
t.Fatalf("Expected:\n%s\n\n but got\n%s", expectedJson, gotJson)
10191019
}
10201020
}
1021+
1022+
func Test_RemoveFields(t *testing.T) {
1023+
testcases := []struct {
1024+
OldJson string
1025+
Fields []string
1026+
ExpectJson string
1027+
}{
1028+
{
1029+
OldJson: `
1030+
{
1031+
"apiVersion": "2024-05-01",
1032+
"id": "/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/acctestheng125/providers/Microsoft.Network/routeTables/acctestheng125",
1033+
"name": "acctestheng125",
1034+
"type": "microsoft.network/routetables",
1035+
"location": "westus",
1036+
"properties": {
1037+
"provisioningState": "Succeeded",
1038+
"resourceGuid": "c7e6268d-eef3-4aa0-86a2-9a2cdedd59a8",
1039+
"disableBgpRoutePropagation": false,
1040+
"routes": [
1041+
{
1042+
"name": "route1",
1043+
"id": "/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/acctestheng125/providers/Microsoft.Network/routeTables/acctestheng125/routes/route1",
1044+
"etag": "W/\"8cbb63f5-3125-4a0b-86d0-a4818311b154\"",
1045+
"properties": {
1046+
"provisioningState": "Succeeded",
1047+
"addressPrefix": "10.1.0.0/16",
1048+
"nextHopType": "VnetLocal",
1049+
"nextHopIpAddress": "",
1050+
"hasBgpOverride": false
1051+
},
1052+
"type": "Microsoft.Network/routeTables/routes"
1053+
}
1054+
]
1055+
},
1056+
"etag": "W/\"8cbb63f5-3125-4a0b-86d0-a4818311b154\""
1057+
}
1058+
`,
1059+
Fields: []string{"etag"},
1060+
ExpectJson: `
1061+
{
1062+
"apiVersion": "2024-05-01",
1063+
"id": "/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/acctestheng125/providers/Microsoft.Network/routeTables/acctestheng125",
1064+
"name": "acctestheng125",
1065+
"type": "microsoft.network/routetables",
1066+
"location": "westus",
1067+
"properties": {
1068+
"provisioningState": "Succeeded",
1069+
"resourceGuid": "c7e6268d-eef3-4aa0-86a2-9a2cdedd59a8",
1070+
"disableBgpRoutePropagation": false,
1071+
"routes": [
1072+
{
1073+
"name": "route1",
1074+
"id": "/subscriptions/00000000-0000-0000-0000-00000000000/resourceGroups/acctestheng125/providers/Microsoft.Network/routeTables/acctestheng125/routes/route1",
1075+
"properties": {
1076+
"provisioningState": "Succeeded",
1077+
"addressPrefix": "10.1.0.0/16",
1078+
"nextHopType": "VnetLocal",
1079+
"nextHopIpAddress": "",
1080+
"hasBgpOverride": false
1081+
},
1082+
"type": "Microsoft.Network/routeTables/routes"
1083+
}
1084+
]
1085+
}
1086+
}
1087+
`,
1088+
},
1089+
}
1090+
1091+
for _, testcase := range testcases {
1092+
var old, expected interface{}
1093+
_ = json.Unmarshal([]byte(testcase.OldJson), &old)
1094+
_ = json.Unmarshal([]byte(testcase.ExpectJson), &expected)
1095+
1096+
result := utils.RemoveFields(old, testcase.Fields)
1097+
if !reflect.DeepEqual(result, expected) {
1098+
expectedJson, _ := json.Marshal(expected)
1099+
resultJson, _ := json.Marshal(result)
1100+
t.Fatalf("Expected %s but got %s", expectedJson, resultJson)
1101+
}
1102+
}
1103+
}

0 commit comments

Comments
 (0)