From 68859a3fad8055143faf2474249f16b7fb69eed1 Mon Sep 17 00:00:00 2001 From: Alexander Dahmen Date: Fri, 28 Mar 2025 13:31:36 +0100 Subject: [PATCH] fix(server): Handle boot bolume correctly (#749) * fix(server): Handle boot bolume correctly - Display id and delete_on_termination in datasource - Handle id and delete_on_termination in resource Signed-off-by: Alexander Dahmen * fixup Signed-off-by: Alexander Dahmen --------- Signed-off-by: Alexander Dahmen --- docs/data-sources/server.md | 5 +-- docs/data-sources/server_backup_schedule.md | 2 - docs/data-sources/server_backup_schedules.md | 2 - docs/resources/server.md | 4 ++ docs/resources/server_backup_schedule.md | 2 - .../internal/services/iaas/iaas_acc_test.go | 8 +++- .../services/iaas/server/datasource.go | 34 ++++++++++------- .../internal/services/iaas/server/resource.go | 37 +++++++++++++++++++ .../services/iaas/server/resource_test.go | 2 + 9 files changed, 70 insertions(+), 26 deletions(-) diff --git a/docs/data-sources/server.md b/docs/data-sources/server.md index ff7d0ab8..99c1ae9f 100644 --- a/docs/data-sources/server.md +++ b/docs/data-sources/server.md @@ -43,7 +43,4 @@ Server datasource schema. Must have a `region` specified in the provider configu Read-Only: - `delete_on_termination` (Boolean) Delete the volume during the termination of the server. -- `id` (String) The ID of the source, either image ID or volume ID -- `performance_class` (String) The performance class of the server. -- `size` (Number) The size of the boot volume in GB. -- `type` (String) The type of the source. Supported values are: `volume`, `image`. +- `id` (String) The ID of the boot volume diff --git a/docs/data-sources/server_backup_schedule.md b/docs/data-sources/server_backup_schedule.md index 99fccef2..0d5422a2 100644 --- a/docs/data-sources/server_backup_schedule.md +++ b/docs/data-sources/server_backup_schedule.md @@ -52,5 +52,3 @@ Read-Only: - `name` (String) - `retention_period` (Number) - `volume_ids` (List of String) - - diff --git a/docs/data-sources/server_backup_schedules.md b/docs/data-sources/server_backup_schedules.md index 71fe6c2c..30b3cf9b 100644 --- a/docs/data-sources/server_backup_schedules.md +++ b/docs/data-sources/server_backup_schedules.md @@ -58,5 +58,3 @@ Read-Only: - `name` (String) - `retention_period` (Number) - `volume_ids` (List of String) - - diff --git a/docs/resources/server.md b/docs/resources/server.md index b49e3299..212c44a3 100644 --- a/docs/resources/server.md +++ b/docs/resources/server.md @@ -410,3 +410,7 @@ Optional: - `delete_on_termination` (Boolean) Delete the volume during the termination of the server. Only allowed when `source_type` is `image`. - `performance_class` (String) The performance class of the server. - `size` (Number) The size of the boot volume in GB. Must be provided when `source_type` is `image`. + +Read-Only: + +- `id` (String) The ID of the boot volume diff --git a/docs/resources/server_backup_schedule.md b/docs/resources/server_backup_schedule.md index d7d3e3b9..0d25b464 100644 --- a/docs/resources/server_backup_schedule.md +++ b/docs/resources/server_backup_schedule.md @@ -62,5 +62,3 @@ Required: Optional: - `volume_ids` (List of String) - - diff --git a/stackit/internal/services/iaas/iaas_acc_test.go b/stackit/internal/services/iaas/iaas_acc_test.go index 50d4da6f..2ebd32c3 100644 --- a/stackit/internal/services/iaas/iaas_acc_test.go +++ b/stackit/internal/services/iaas/iaas_acc_test.go @@ -733,6 +733,7 @@ func TestAccServer(t *testing.T) { // The network interface which was attached by "stackit_server_network_interface_attach" should not appear here resource.TestCheckResourceAttr("stackit_server.server", "network_interfaces.#", "1"), resource.TestCheckNoResourceAttr("stackit_server.server", "network_interfaces.1"), + resource.TestCheckResourceAttrSet("stackit_server.server", "boot_volume.id"), resource.TestCheckResourceAttr("stackit_server.server", "boot_volume.size", serverResource["size"]), resource.TestCheckResourceAttr("stackit_server.server", "boot_volume.source_type", serverResource["source_type"]), resource.TestCheckResourceAttr("stackit_server.server", "boot_volume.source_id", serverResource["source_id"]), @@ -871,6 +872,9 @@ func TestAccServer(t *testing.T) { ), resource.TestCheckResourceAttrSet("stackit_network_interface.network_interface", "network_interface_id"), resource.TestCheckResourceAttr("stackit_network_interface.network_interface", "name", networkInterfaceResource["name"]), + // Boot volume + resource.TestCheckResourceAttrSet("data.stackit_server.server", "boot_volume.id"), + resource.TestCheckResourceAttr("data.stackit_server.server", "boot_volume.delete_on_termination", serverResource["delete_on_termination"]), ), }, // Import @@ -1028,8 +1032,8 @@ func TestAccServer(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"), resource.TestCheckResourceAttr("stackit_network.network", "name", fmt.Sprintf("%s-updated", networkResource["name"])), resource.TestCheckResourceAttr("stackit_network.network", "nameservers.#", "2"), - resource.TestCheckResourceAttr("stackit_network.network", "nameservers.0", networkResource["nameserver0"]), - resource.TestCheckResourceAttr("stackit_network.network", "nameservers.1", networkResource["nameserver1"]), + resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver0"]), + resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver1"]), resource.TestCheckResourceAttr("stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]), // Server diff --git a/stackit/internal/services/iaas/server/datasource.go b/stackit/internal/services/iaas/server/datasource.go index a8190a77..3e806028 100644 --- a/stackit/internal/services/iaas/server/datasource.go +++ b/stackit/internal/services/iaas/server/datasource.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -18,7 +19,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/services/iaas" "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" ) @@ -46,6 +46,11 @@ type DataSourceModel struct { UpdatedAt types.String `tfsdk:"updated_at"` } +var bootVolumeDataTypes = map[string]attr.Type{ + "id": basetypes.StringType{}, + "delete_on_termination": basetypes.BoolType{}, +} + // NewServerDataSource is a helper function to simplify the provider implementation. func NewServerDataSource() datasource.DataSource { return &serverDataSource{} @@ -139,20 +144,8 @@ func (r *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, Description: "The boot volume for the server", Computed: true, Attributes: map[string]schema.Attribute{ - "performance_class": schema.StringAttribute{ - Description: "The performance class of the server.", - Computed: true, - }, - "size": schema.Int64Attribute{ - Description: "The size of the boot volume in GB.", - Computed: true, - }, - "type": schema.StringAttribute{ - Description: "The type of the source. " + utils.SupportedValuesDocumentation(supportedSourceTypes), - Computed: true, - }, "id": schema.StringAttribute{ - Description: "The ID of the source, either image ID or volume ID", + Description: "The ID of the boot volume", Computed: true, }, "delete_on_termination": schema.BoolAttribute{ @@ -312,6 +305,19 @@ func mapDataSourceFields(ctx context.Context, serverResp *iaas.Server, model *Da model.NetworkInterfaces = types.ListNull(types.StringType) } + if serverResp.BootVolume != nil { + bootVolume, diags := types.ObjectValue(bootVolumeDataTypes, map[string]attr.Value{ + "id": types.StringPointerValue(serverResp.BootVolume.Id), + "delete_on_termination": types.BoolPointerValue(serverResp.BootVolume.DeleteOnTermination), + }) + if diags.HasError() { + return fmt.Errorf("failed to map bootVolume: %w", core.DiagsToError(diags)) + } + model.BootVolume = bootVolume + } else { + model.BootVolume = types.ObjectNull(bootVolumeDataTypes) + } + model.AvailabilityZone = types.StringPointerValue(serverResp.AvailabilityZone) model.ServerId = types.StringValue(serverId) model.MachineType = types.StringPointerValue(serverResp.MachineType) diff --git a/stackit/internal/services/iaas/server/resource.go b/stackit/internal/services/iaas/server/resource.go index d3d0c00d..ab51d661 100644 --- a/stackit/internal/services/iaas/server/resource.go +++ b/stackit/internal/services/iaas/server/resource.go @@ -75,6 +75,7 @@ type Model struct { // Struct corresponding to Model.BootVolume type bootVolumeModel struct { + Id types.String `tfsdk:"id"` PerformanceClass types.String `tfsdk:"performance_class"` Size types.Int64 `tfsdk:"size"` SourceType types.String `tfsdk:"source_type"` @@ -89,6 +90,7 @@ var bootVolumeTypes = map[string]attr.Type{ "source_type": basetypes.StringType{}, "source_id": basetypes.StringType{}, "delete_on_termination": basetypes.BoolType{}, + "id": basetypes.StringType{}, } // NewServerResource is a helper function to simplify the provider implementation. @@ -253,6 +255,13 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res objectplanmodifier.RequiresReplace(), }, Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the boot volume", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "performance_class": schema.StringAttribute{ Description: "The performance class of the server.", Optional: true, @@ -911,6 +920,34 @@ func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model) error model.NetworkInterfaces = types.ListNull(types.StringType) } + if serverResp.BootVolume != nil { + // convert boot volume model + var bootVolumeModel = &bootVolumeModel{} + if !(model.BootVolume.IsNull() || model.BootVolume.IsUnknown()) { + diags := model.BootVolume.As(ctx, bootVolumeModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fmt.Errorf("failed to map bootVolume: %w", core.DiagsToError(diags)) + } + } + + // Only the id and delete_on_termination is returned via response. + // Take the other values from the model. + bootVolume, diags := types.ObjectValue(bootVolumeTypes, map[string]attr.Value{ + "id": types.StringPointerValue(serverResp.BootVolume.Id), + "delete_on_termination": types.BoolPointerValue(serverResp.BootVolume.DeleteOnTermination), + "source_id": bootVolumeModel.SourceId, + "size": bootVolumeModel.Size, + "source_type": bootVolumeModel.SourceType, + "performance_class": bootVolumeModel.PerformanceClass, + }) + if diags.HasError() { + return fmt.Errorf("failed to map bootVolume: %w", core.DiagsToError(diags)) + } + model.BootVolume = bootVolume + } else { + model.BootVolume = types.ObjectNull(bootVolumeTypes) + } + model.ServerId = types.StringValue(serverId) model.MachineType = types.StringPointerValue(serverResp.MachineType) diff --git a/stackit/internal/services/iaas/server/resource_test.go b/stackit/internal/services/iaas/server/resource_test.go index 49798c1d..d9dac877 100644 --- a/stackit/internal/services/iaas/server/resource_test.go +++ b/stackit/internal/services/iaas/server/resource_test.go @@ -193,6 +193,7 @@ func TestToCreatePayload(t *testing.T) { "source_type": types.StringValue("type"), "source_id": types.StringValue("id"), "delete_on_termination": types.BoolUnknown(), + "id": types.StringValue("id"), }), ImageId: types.StringValue("image"), KeypairName: types.StringValue("keypair"), @@ -234,6 +235,7 @@ func TestToCreatePayload(t *testing.T) { "source_type": types.StringValue("image"), "source_id": types.StringValue("id"), "delete_on_termination": types.BoolValue(true), + "id": types.StringValue("id"), }), ImageId: types.StringValue("image"), KeypairName: types.StringValue("keypair"),