Onboard MongoDB Flex instance (#86)
* Onboard instance resource * Add options.type as required field * Implement resource unit tests * Implement data source * Implement acc tests * Adjust update acc test * Fix typo * Adjust update unit tests * Adjustments after review * Minor adjustment for uniformity * Adjustments after review
This commit is contained in:
parent
6372434e56
commit
ade77eb544
12 changed files with 2245 additions and 106 deletions
|
|
@ -64,7 +64,7 @@ var flavorTypes = map[string]attr.Type{
|
|||
"ram": basetypes.Int64Type{},
|
||||
}
|
||||
|
||||
// Struct corresponding to DataSourceModel.Storage
|
||||
// Struct corresponding to Model.Storage
|
||||
type storageModel struct {
|
||||
Class types.String `tfsdk:"class"`
|
||||
Size types.Int64 `tfsdk:"size"`
|
||||
|
|
@ -261,8 +261,9 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.loadFlavorId(ctx, &resp.Diagnostics, &model, flavor)
|
||||
if resp.Diagnostics.HasError() {
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading flavor ID: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -395,8 +396,9 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.loadFlavorId(ctx, &resp.Diagnostics, &model, flavor)
|
||||
if resp.Diagnostics.HasError() {
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading flavor ID: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -435,7 +437,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
// Map response body to schema
|
||||
err = mapFields(got, &model, flavor, storage)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error mapping fields in update", err.Error())
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
diags = resp.State.Set(ctx, model)
|
||||
|
|
@ -524,15 +526,15 @@ func mapFields(resp *postgresflex.InstanceResponse, model *Model, flavor *flavor
|
|||
}
|
||||
aclList, diags = types.ListValue(types.StringType, acl)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("failed to map ACL: %w", core.DiagsToError(diags))
|
||||
return fmt.Errorf("mapping ACL: %w", core.DiagsToError(diags))
|
||||
}
|
||||
}
|
||||
|
||||
var flavorValues map[string]attr.Value
|
||||
if instance.Flavor == nil {
|
||||
flavorValues = map[string]attr.Value{
|
||||
"id": types.StringNull(),
|
||||
"description": types.StringNull(),
|
||||
"id": flavor.Id,
|
||||
"description": flavor.Description,
|
||||
"cpu": flavor.CPU,
|
||||
"ram": flavor.RAM,
|
||||
}
|
||||
|
|
@ -540,13 +542,13 @@ func mapFields(resp *postgresflex.InstanceResponse, model *Model, flavor *flavor
|
|||
flavorValues = map[string]attr.Value{
|
||||
"id": types.StringValue(*instance.Flavor.Id),
|
||||
"description": types.StringValue(*instance.Flavor.Description),
|
||||
"cpu": types.Int64Value(int64(*instance.Flavor.Cpu)),
|
||||
"ram": types.Int64Value(int64(*instance.Flavor.Memory)),
|
||||
"cpu": conversion.ToTypeInt64(instance.Flavor.Cpu),
|
||||
"ram": conversion.ToTypeInt64(instance.Flavor.Memory),
|
||||
}
|
||||
}
|
||||
flavorObject, diags := types.ObjectValue(flavorTypes, flavorValues)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("failed to create flavor: %w", core.DiagsToError(diags))
|
||||
return fmt.Errorf("creating flavor: %w", core.DiagsToError(diags))
|
||||
}
|
||||
|
||||
var storageValues map[string]attr.Value
|
||||
|
|
@ -558,12 +560,12 @@ func mapFields(resp *postgresflex.InstanceResponse, model *Model, flavor *flavor
|
|||
} else {
|
||||
storageValues = map[string]attr.Value{
|
||||
"class": types.StringValue(*instance.Storage.Class),
|
||||
"size": types.Int64Value(int64(*instance.Storage.Size)),
|
||||
"size": conversion.ToTypeInt64(instance.Storage.Size),
|
||||
}
|
||||
}
|
||||
storageObject, diags := types.ObjectValue(storageTypes, storageValues)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("failed to create storage: %w", core.DiagsToError(diags))
|
||||
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
|
||||
}
|
||||
|
||||
idParts := []string{
|
||||
|
|
@ -574,29 +576,13 @@ func mapFields(resp *postgresflex.InstanceResponse, model *Model, flavor *flavor
|
|||
strings.Join(idParts, core.Separator),
|
||||
)
|
||||
model.InstanceId = types.StringValue(instanceId)
|
||||
if instance.Name == nil {
|
||||
model.Name = types.StringNull()
|
||||
} else {
|
||||
model.Name = types.StringValue(*instance.Name)
|
||||
}
|
||||
model.Name = types.StringPointerValue(instance.Name)
|
||||
model.ACL = aclList
|
||||
if instance.BackupSchedule == nil {
|
||||
model.BackupSchedule = types.StringNull()
|
||||
} else {
|
||||
model.BackupSchedule = types.StringValue(*instance.BackupSchedule)
|
||||
}
|
||||
model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
|
||||
model.Flavor = flavorObject
|
||||
if instance.Replicas == nil {
|
||||
model.Replicas = types.Int64Null()
|
||||
} else {
|
||||
model.Replicas = types.Int64Value(int64(*instance.Replicas))
|
||||
}
|
||||
model.Replicas = conversion.ToTypeInt64(instance.Replicas)
|
||||
model.Storage = storageObject
|
||||
if instance.Version == nil {
|
||||
model.Version = types.StringNull()
|
||||
} else {
|
||||
model.Version = types.StringValue(*instance.Version)
|
||||
}
|
||||
model.Version = types.StringPointerValue(instance.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -660,37 +646,35 @@ func toUpdatePayload(model *Model, acl []string, flavor *flavorModel, storage *s
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (r *instanceResource) loadFlavorId(ctx context.Context, diags *diag.Diagnostics, model *Model, flavor *flavorModel) {
|
||||
type postgresFlexClient interface {
|
||||
GetFlavorsExecute(ctx context.Context, projectId string) (*postgresflex.FlavorsResponse, error)
|
||||
}
|
||||
|
||||
func loadFlavorId(ctx context.Context, client postgresFlexClient, model *Model, flavor *flavorModel) error {
|
||||
if model == nil {
|
||||
diags.AddError("invalid model", "nil model")
|
||||
return
|
||||
return fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
diags.AddError("invalid flavor", "nil flavor")
|
||||
return
|
||||
return fmt.Errorf("nil flavor")
|
||||
}
|
||||
cpu := conversion.ToPtrInt32(flavor.CPU)
|
||||
if cpu == nil {
|
||||
diags.AddError("invalid flavor", "nil CPU")
|
||||
return
|
||||
return fmt.Errorf("nil CPU")
|
||||
}
|
||||
ram := conversion.ToPtrInt32(flavor.RAM)
|
||||
if ram == nil {
|
||||
diags.AddError("invalid flavor", "nil RAM")
|
||||
return
|
||||
return fmt.Errorf("nil RAM")
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
res, err := r.client.GetFlavors(ctx, projectId).Execute()
|
||||
res, err := client.GetFlavorsExecute(ctx, projectId)
|
||||
if err != nil {
|
||||
diags.AddError("failed to list postgresflex flavors", err.Error())
|
||||
return
|
||||
return fmt.Errorf("listing postgresflex flavors: %w", err)
|
||||
}
|
||||
|
||||
avl := ""
|
||||
if res.Flavors == nil {
|
||||
diags.AddError("no flavors", fmt.Sprintf("couldn't find flavors for id %s", flavor.Id.ValueString()))
|
||||
return
|
||||
return fmt.Errorf("finding flavors for project %s", projectId)
|
||||
}
|
||||
for _, f := range *res.Flavors {
|
||||
if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
|
|
@ -698,12 +682,14 @@ func (r *instanceResource) loadFlavorId(ctx context.Context, diags *diag.Diagnos
|
|||
}
|
||||
if *f.Cpu == *cpu && *f.Memory == *ram {
|
||||
flavor.Id = types.StringValue(*f.Id)
|
||||
flavor.Description = types.StringValue(*f.Description)
|
||||
break
|
||||
}
|
||||
avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Cpu)
|
||||
}
|
||||
if flavor.Id.ValueString() == "" {
|
||||
diags.AddError("invalid flavor", fmt.Sprintf("couldn't find flavor.\navailable specs are:%s", avl))
|
||||
return
|
||||
return fmt.Errorf("couldn't find flavor, available specs are:%s", avl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package postgresflex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
|
@ -10,6 +12,19 @@ import (
|
|||
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex"
|
||||
)
|
||||
|
||||
type postgresFlexClientMocked struct {
|
||||
returnError bool
|
||||
getFlavorsResp *postgresflex.FlavorsResponse
|
||||
}
|
||||
|
||||
func (c *postgresFlexClientMocked) GetFlavorsExecute(_ context.Context, _ string) (*postgresflex.FlavorsResponse, error) {
|
||||
if c.returnError {
|
||||
return nil, fmt.Errorf("get flavors failed")
|
||||
}
|
||||
|
||||
return c.getFlavorsResp, nil
|
||||
}
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
|
|
@ -507,3 +522,156 @@ func TestToUpdatePayload(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFlavorId(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
inputFlavor *flavorModel
|
||||
mockedResp *postgresflex.FlavorsResponse
|
||||
expected *flavorModel
|
||||
getFlavorsFails bool
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
"ok_flavor",
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
&postgresflex.FlavorsResponse{
|
||||
Flavors: &[]postgresflex.InstanceFlavor{
|
||||
{
|
||||
Id: utils.Ptr("fid-1"),
|
||||
Cpu: utils.Ptr(int32(2)),
|
||||
Description: utils.Ptr("description"),
|
||||
Memory: utils.Ptr(int32(8)),
|
||||
},
|
||||
},
|
||||
},
|
||||
&flavorModel{
|
||||
Id: types.StringValue("fid-1"),
|
||||
Description: types.StringValue("description"),
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ok_flavor_2",
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
&postgresflex.FlavorsResponse{
|
||||
Flavors: &[]postgresflex.InstanceFlavor{
|
||||
{
|
||||
Id: utils.Ptr("fid-1"),
|
||||
Cpu: utils.Ptr(int32(2)),
|
||||
Description: utils.Ptr("description"),
|
||||
Memory: utils.Ptr(int32(8)),
|
||||
},
|
||||
{
|
||||
Id: utils.Ptr("fid-2"),
|
||||
Cpu: utils.Ptr(int32(1)),
|
||||
Description: utils.Ptr("description"),
|
||||
Memory: utils.Ptr(int32(4)),
|
||||
},
|
||||
},
|
||||
},
|
||||
&flavorModel{
|
||||
Id: types.StringValue("fid-1"),
|
||||
Description: types.StringValue("description"),
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"no_matching_flavor",
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
&postgresflex.FlavorsResponse{
|
||||
Flavors: &[]postgresflex.InstanceFlavor{
|
||||
{
|
||||
Id: utils.Ptr("fid-1"),
|
||||
Cpu: utils.Ptr(int32(1)),
|
||||
Description: utils.Ptr("description"),
|
||||
Memory: utils.Ptr(int32(8)),
|
||||
},
|
||||
{
|
||||
Id: utils.Ptr("fid-2"),
|
||||
Cpu: utils.Ptr(int32(1)),
|
||||
Description: utils.Ptr("description"),
|
||||
Memory: utils.Ptr(int32(4)),
|
||||
},
|
||||
},
|
||||
},
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"nil_response",
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
&postgresflex.FlavorsResponse{},
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"error_response",
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
&postgresflex.FlavorsResponse{},
|
||||
&flavorModel{
|
||||
CPU: types.Int64Value(2),
|
||||
RAM: types.Int64Value(8),
|
||||
},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
client := &postgresFlexClientMocked{
|
||||
returnError: tt.getFlavorsFails,
|
||||
getFlavorsResp: tt.mockedResp,
|
||||
}
|
||||
model := &Model{
|
||||
ProjectId: types.StringValue("pid"),
|
||||
}
|
||||
flavorModel := &flavorModel{
|
||||
CPU: tt.inputFlavor.CPU,
|
||||
RAM: tt.inputFlavor.RAM,
|
||||
}
|
||||
err := loadFlavorId(context.Background(), client, model, flavorModel)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("Should not have failed: %v", err)
|
||||
}
|
||||
if tt.isValid {
|
||||
diff := cmp.Diff(flavorModel, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue