Bugfix inconsistent applies of ListAttributes (#328)

* sort the list of ACLs for MongoDBFlex

* Fix and test other cases of ListAttribute ordering

* fix linting

* revert sorting changes, introduce new reconcilestrlist function

* merge main

* Fix rabbitmq

* fix segmentation fault

* Improve testing

* pass context to mapfields, minor name fixes
This commit is contained in:
Diogo Ferrão 2024-04-16 11:20:19 +01:00 committed by GitHub
parent 18d3f4d1fb
commit 9bd1da7cee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1011 additions and 146 deletions

View file

@ -213,7 +213,7 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
}
}
err = mapFields(instanceResp, &model, flavor, storage, options)
err = mapFields(ctx, instanceResp, &model, flavor, storage, options)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return

View file

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/resource"
@ -328,7 +329,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
}
// Map response body to schema
err = mapFields(waitResp, &model, flavor, storage, options)
err = mapFields(ctx, waitResp, &model, flavor, storage, options)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
return
@ -388,7 +389,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
}
// Map response body to schema
err = mapFields(instanceResp, &model, flavor, storage, options)
err = mapFields(ctx, instanceResp, &model, flavor, storage, options)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return
@ -474,7 +475,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
}
// Map response body to schema
err = mapFields(waitResp, &model, flavor, storage, options)
err = mapFields(ctx, waitResp, &model, flavor, storage, options)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
return
@ -533,7 +534,7 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
tflog.Info(ctx, "MongoDB Flex instance state imported")
}
func mapFields(resp *mongodbflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, options *optionsModel) error {
func mapFields(ctx context.Context, resp *mongodbflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, options *optionsModel) error {
if resp == nil {
return fmt.Errorf("response input is nil")
}
@ -559,11 +560,15 @@ func mapFields(resp *mongodbflex.GetInstanceResponse, model *Model, flavor *flav
if instance.Acl == nil || instance.Acl.Items == nil {
aclList = types.ListNull(types.StringType)
} else {
acl := []attr.Value{}
for _, ip := range *instance.Acl.Items {
acl = append(acl, types.StringValue(ip))
respACL := *instance.Acl.Items
modelACL, err := utils.ListValuetoStringSlice(model.ACL)
if err != nil {
return err
}
aclList, diags = types.ListValue(types.StringType, acl)
reconciledACL := utils.ReconcileStringSlices(modelACL, respACL)
aclList, diags = types.ListValueFrom(ctx, types.StringType, reconciledACL)
if diags.HasError() {
return fmt.Errorf("mapping ACL: %w", core.DiagsToError(diags))
}

View file

@ -28,6 +28,7 @@ func (c *mongoDBFlexClientMocked) ListFlavorsExecute(_ context.Context, _ string
func TestMapFields(t *testing.T) {
tests := []struct {
description string
state Model
input *mongodbflex.GetInstanceResponse
flavor *flavorModel
storage *storageModel
@ -37,6 +38,10 @@ func TestMapFields(t *testing.T) {
}{
{
"default_values",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{},
},
@ -70,6 +75,10 @@ func TestMapFields(t *testing.T) {
},
{
"simple_values",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{
Acl: &mongodbflex.ACL{
@ -134,6 +143,10 @@ func TestMapFields(t *testing.T) {
},
{
"simple_values_no_flavor_and_storage",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{
Acl: &mongodbflex.ACL{
@ -196,8 +209,85 @@ func TestMapFields(t *testing.T) {
},
true,
},
{
"acls_unordered",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ip2"),
types.StringValue(""),
types.StringValue("ip1"),
}),
},
&mongodbflex.GetInstanceResponse{
Item: &mongodbflex.Instance{
Acl: &mongodbflex.ACL{
Items: &[]string{
"",
"ip1",
"ip2",
},
},
BackupSchedule: utils.Ptr("schedule"),
Flavor: nil,
Id: utils.Ptr("iid"),
Name: utils.Ptr("name"),
Replicas: utils.Ptr(int64(56)),
Status: utils.Ptr("status"),
Storage: nil,
Options: &map[string]string{
"type": "type",
},
Version: utils.Ptr("version"),
},
},
&flavorModel{
CPU: types.Int64Value(12),
RAM: types.Int64Value(34),
},
&storageModel{
Class: types.StringValue("class"),
Size: types.Int64Value(78),
},
&optionsModel{
Type: types.StringValue("type"),
},
Model{
Id: types.StringValue("pid,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ip2"),
types.StringValue(""),
types.StringValue("ip1"),
}),
BackupSchedule: types.StringValue("schedule"),
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringNull(),
"description": types.StringNull(),
"cpu": types.Int64Value(12),
"ram": types.Int64Value(34),
}),
Replicas: types.Int64Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringValue("class"),
"size": types.Int64Value(78),
}),
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
"type": types.StringValue("type"),
}),
Version: types.StringValue("version"),
},
true,
},
{
"nil_response",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
nil,
&flavorModel{},
&storageModel{},
@ -207,6 +297,10 @@ func TestMapFields(t *testing.T) {
},
{
"no_resource_id",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&mongodbflex.GetInstanceResponse{},
&flavorModel{},
&storageModel{},
@ -217,11 +311,7 @@ func TestMapFields(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
state := &Model{
ProjectId: tt.expected.ProjectId,
InstanceId: tt.expected.InstanceId,
}
err := mapFields(tt.input, state, tt.flavor, tt.storage, tt.options)
err := mapFields(context.Background(), tt.input, &tt.state, tt.flavor, tt.storage, tt.options)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@ -229,7 +319,7 @@ func TestMapFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
diff := cmp.Diff(tt.state, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}