Allow deleting root volume when deleting server (#651)

Introduce delete_on_termination field.

Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
This commit is contained in:
Alexander Dahmen 2025-01-29 09:23:11 +01:00 committed by GitHub
parent 11875602b8
commit b6f3c70f15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 100 additions and 14 deletions

View file

@ -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{

View file

@ -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"
@ -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.
@ -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{
@ -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{
@ -359,7 +392,7 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
Computed: true,
},
"desired_status": schema.StringAttribute{
Description: "The desired status of the server resource." + utils.SupportedValuesDocumentation(desiredStatusOptions),
Description: "The desired status of the server resource. " + utils.SupportedValuesDocumentation(desiredStatusOptions),
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf(desiredStatusOptions...),
@ -908,6 +941,10 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
Type: conversion.StringValueToPointer(bootVolume.SourceType),
},
}
if !bootVolume.DeleteOnTermination.IsNull() && !bootVolume.DeleteOnTermination.IsUnknown() && bootVolume.DeleteOnTermination.ValueBool() {
// it is set and true, adjust payload
bootVolumePayload.DeleteOnTermination = conversion.BoolValueToPointer(bootVolume.DeleteOnTermination)
}
}
var userData *string

View file

@ -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"),
@ -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) {