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

@ -193,7 +193,7 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
}
}
err = mapFields(instanceResp, &model, flavor, storage)
err = mapFields(ctx, instanceResp, &model, flavor, storage)
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/path"
@ -297,7 +298,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
}
// Map response body to schema
err = mapFields(waitResp, &model, flavor, storage)
err = mapFields(ctx, waitResp, &model, flavor, storage)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
return
@ -348,7 +349,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
}
// Map response body to schema
err = mapFields(instanceResp, &model, flavor, storage)
err = mapFields(ctx, instanceResp, &model, flavor, storage)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return
@ -425,7 +426,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
}
// Map response body to schema
err = mapFields(waitResp, &model, flavor, storage)
err = mapFields(ctx, waitResp, &model, flavor, storage)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
return
@ -484,7 +485,7 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
tflog.Info(ctx, "Postgresql instance state imported")
}
func mapFields(resp *postgresflex.InstanceResponse, model *Model, flavor *flavorModel, storage *storageModel) error {
func mapFields(ctx context.Context, resp *postgresflex.InstanceResponse, model *Model, flavor *flavorModel, storage *storageModel) error {
if resp == nil {
return fmt.Errorf("response input is nil")
}
@ -510,11 +511,15 @@ func mapFields(resp *postgresflex.InstanceResponse, model *Model, flavor *flavor
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 *postgresFlexClientMocked) ListFlavorsExecute(_ context.Context, _ strin
func TestMapFields(t *testing.T) {
tests := []struct {
description string
state Model
input *postgresflex.InstanceResponse
flavor *flavorModel
storage *storageModel
@ -36,6 +37,10 @@ func TestMapFields(t *testing.T) {
}{
{
"default_values",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.InstanceResponse{
Item: &postgresflex.Instance{},
},
@ -65,6 +70,10 @@ func TestMapFields(t *testing.T) {
},
{
"simple_values",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.InstanceResponse{
Item: &postgresflex.Instance{
Acl: &postgresflex.ACL{
@ -122,6 +131,10 @@ func TestMapFields(t *testing.T) {
},
{
"simple_values_no_flavor_and_storage",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.InstanceResponse{
Item: &postgresflex.Instance{
Acl: &postgresflex.ACL{
@ -175,8 +188,76 @@ func TestMapFields(t *testing.T) {
},
true,
},
{
"acl_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"),
}),
},
&postgresflex.InstanceResponse{
Item: &postgresflex.Instance{
Acl: &postgresflex.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,
Version: utils.Ptr("version"),
},
},
&flavorModel{
CPU: types.Int64Value(12),
RAM: types.Int64Value(34),
},
&storageModel{
Class: types.StringValue("class"),
Size: types.Int64Value(78),
},
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),
}),
Version: types.StringValue("version"),
},
true,
},
{
"nil_response",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
nil,
&flavorModel{},
&storageModel{},
@ -185,6 +266,10 @@ func TestMapFields(t *testing.T) {
},
{
"no_resource_id",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.InstanceResponse{},
&flavorModel{},
&storageModel{},
@ -194,11 +279,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)
err := mapFields(context.Background(), tt.input, &tt.state, tt.flavor, tt.storage)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@ -206,7 +287,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)
}