Skip to content

Commit

Permalink
Allow deleting root volume when deleting server
Browse files Browse the repository at this point in the history
Introduce delete_on_termination field.

Signed-off-by: Alexander Dahmen <[email protected]>
  • Loading branch information
Fyusel committed Jan 27, 2025
1 parent a2ed2b6 commit b0030f6
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 13 deletions.
1 change: 1 addition & 0 deletions docs/data-sources/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ 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.
Expand Down
3 changes: 2 additions & 1 deletion docs/resources/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ resource "stackit_server" "user-data-from-file" {
- `affinity_group` (String) The affinity group the server is assigned to.
- `availability_zone` (String) The availability zone of the server.
- `boot_volume` (Attributes) The boot volume for the server (see [below for nested schema](#nestedatt--boot_volume))
- `desired_status` (String) The desired status of the server resource. Defaults to 'active' Supported values are: `active`, `inactive`, `deallocated`.
- `desired_status` (String) The desired status of the server resource.Supported values are: `active`, `inactive`, `deallocated`.
- `image_id` (String) The image ID to be used for an ephemeral disk on the server.
- `keypair_name` (String) The name of the keypair used during server creation.
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
Expand All @@ -409,5 +409,6 @@ Required:

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`.
4 changes: 4 additions & 0 deletions stackit/internal/services/iaas/server/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ func (r *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
Description: "The ID of the source, either image ID or volume ID",
Computed: true,
},
"delete_on_termination": schema.BoolAttribute{
Description: "Delete the volume during the termination of the server.",
Computed: true,
},
},
},
"image_id": schema.StringAttribute{
Expand Down
53 changes: 45 additions & 8 deletions stackit/internal/services/iaas/server/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
Expand Down Expand Up @@ -80,18 +81,20 @@ type Model struct {

// Struct corresponding to Model.BootVolume
type bootVolumeModel struct {
PerformanceClass types.String `tfsdk:"performance_class"`
Size types.Int64 `tfsdk:"size"`
SourceType types.String `tfsdk:"source_type"`
SourceId types.String `tfsdk:"source_id"`
PerformanceClass types.String `tfsdk:"performance_class"`
Size types.Int64 `tfsdk:"size"`
SourceType types.String `tfsdk:"source_type"`
SourceId types.String `tfsdk:"source_id"`
DeleteOnTermination types.Bool `tfsdk:"delete_on_termination"`
}

// Types corresponding to bootVolumeModel
var bootVolumeTypes = map[string]attr.Type{
"performance_class": basetypes.StringType{},
"size": basetypes.Int64Type{},
"source_type": basetypes.StringType{},
"source_id": basetypes.StringType{},
"performance_class": basetypes.StringType{},
"size": basetypes.Int64Type{},
"source_type": basetypes.StringType{},
"source_id": basetypes.StringType{},
"delete_on_termination": basetypes.BoolType{},
}

// NewServerResource is a helper function to simplify the provider implementation.
Expand All @@ -109,6 +112,29 @@ func (r *serverResource) Metadata(_ context.Context, req resource.MetadataReques
resp.TypeName = req.ProviderTypeName + "_server"
}

func (r serverResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var model Model
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}

// convert boot volume model
var bootVolume = &bootVolumeModel{}
if !(model.BootVolume.IsNull() || model.BootVolume.IsUnknown()) {
diags := model.BootVolume.As(ctx, bootVolume, basetypes.ObjectAsOptions{})
if diags.HasError() {
return
}
}

if !bootVolume.DeleteOnTermination.IsUnknown() && !bootVolume.DeleteOnTermination.IsNull() && !bootVolume.SourceType.IsUnknown() && !bootVolume.SourceType.IsNull() {
if bootVolume.SourceType != types.StringValue("image") {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring server", "You can only provide `delete_on_termination` for `source_type` `image`.")
}
}
}

// ConfigValidators validates the resource configuration
func (r *serverResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
return []resource.ConfigValidator{
Expand Down Expand Up @@ -276,6 +302,13 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
stringplanmodifier.RequiresReplace(),
},
},
"delete_on_termination": schema.BoolAttribute{
Description: "Delete the volume during the termination of the server. Only allowed when `source_type` is `image`.",
Optional: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.RequiresReplace(),
},
},
},
},
"image_id": schema.StringAttribute{
Expand Down Expand Up @@ -908,6 +941,10 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
Type: conversion.StringValueToPointer(bootVolume.SourceType),
},
}
if !bootVolume.DeleteOnTermination.IsNull() && !bootVolume.DeleteOnTermination.IsNull() && bootVolume.DeleteOnTermination.ValueBool() {

Check failure on line 944 in stackit/internal/services/iaas/server/resource.go

View workflow job for this annotation

GitHub Actions / CI

SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
// it is set and true, adjust payload
bootVolumePayload.DeleteOnTermination = conversion.BoolValueToPointer(bootVolume.DeleteOnTermination)
}
}

var userData *string
Expand Down
51 changes: 47 additions & 4 deletions stackit/internal/services/iaas/server/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ func TestToCreatePayload(t *testing.T) {
"key": types.StringValue("value"),
}),
BootVolume: types.ObjectValueMust(bootVolumeTypes, map[string]attr.Value{
"performance_class": types.StringValue("class"),
"size": types.Int64Value(1),
"source_type": types.StringValue("type"),
"source_id": types.StringValue("id"),
"performance_class": types.StringValue("class"),
"size": types.Int64Value(1),
"source_type": types.StringValue("type"),
"source_id": types.StringValue("id"),
"delete_on_termination": types.BoolUnknown(),
}),
ImageId: types.StringValue("image"),
KeypairName: types.StringValue("keypair"),
Expand Down Expand Up @@ -222,6 +223,48 @@ func TestToCreatePayload(t *testing.T) {
},
true,
},
{
"delete on termination is set to true",
&Model{
Name: types.StringValue("name"),
AvailabilityZone: types.StringValue("zone"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
BootVolume: types.ObjectValueMust(bootVolumeTypes, map[string]attr.Value{
"performance_class": types.StringValue("class"),
"size": types.Int64Value(1),
"source_type": types.StringValue("image"),
"source_id": types.StringValue("id"),
"delete_on_termination": types.BoolValue(true),
}),
ImageId: types.StringValue("image"),
KeypairName: types.StringValue("keypair"),
MachineType: types.StringValue("machine_type"),
UserData: types.StringValue(userData),
},
&iaas.CreateServerPayload{
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
"key": "value",
},
BootVolume: &iaas.CreateServerPayloadBootVolume{
PerformanceClass: utils.Ptr("class"),
Size: utils.Ptr(int64(1)),
Source: &iaas.BootVolumeSource{
Type: utils.Ptr("image"),
Id: utils.Ptr("id"),
},
DeleteOnTermination: utils.Ptr(true),
},
ImageId: utils.Ptr("image"),
KeypairName: utils.Ptr("keypair"),
MachineType: utils.Ptr("machine_type"),
UserData: utils.Ptr(base64EncodedUserData),
},
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
Expand Down

0 comments on commit b0030f6

Please sign in to comment.