feature: Add "network_interfaces" field to server resource (#628)
* Add network_interfaces field to server resource * Update docs * Update description of stackit_server_network_interface_attach Co-authored-by: João Palet <joao.palet@outlook.com> --------- Co-authored-by: João Palet <joao.palet@outlook.com>
This commit is contained in:
parent
9b969ae583
commit
4d6f860b26
6 changed files with 132 additions and 49 deletions
|
|
@ -36,6 +36,7 @@ Server datasource schema. Must have a `region` specified in the provider configu
|
|||
- `launched_at` (String) Date-time when the server was launched
|
||||
- `machine_type` (String) Name of the type of the machine for the server. Possible values are documented in [Virtual machine flavors](https://docs.stackit.cloud/stackit/en/virtual-machine-flavors-75137231.html)
|
||||
- `name` (String) The name of the server.
|
||||
- `network_interfaces` (List of String) The IDs of network interfaces which should be attached to the server. Updating it will recreate the server.
|
||||
- `updated_at` (String) Date-time when the server was updated
|
||||
- `user_data` (String) User data that is passed via cloud-init to the server.
|
||||
|
||||
|
|
|
|||
|
|
@ -388,6 +388,7 @@ resource "stackit_server" "user-data-from-file" {
|
|||
- `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
|
||||
- `network_interfaces` (List of String) The IDs of network interfaces which should be attached to the server. Updating it will recreate the server.
|
||||
- `user_data` (String) User data that is passed via cloud-init to the server.
|
||||
|
||||
### Read-Only
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
page_title: "stackit_server_network_interface_attach Resource - stackit"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Network interface attachment resource schema. Attaches a network interface to a server. Must have a region specified in the provider configuration.
|
||||
Network interface attachment resource schema. Attaches a network interface to a server. Must have a region specified in the provider configuration. The attachment takes only effect after server reboot.
|
||||
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
|
||||
---
|
||||
|
||||
# stackit_server_network_interface_attach (Resource)
|
||||
|
||||
Network interface attachment resource schema. Attaches a network interface to a server. Must have a `region` specified in the provider configuration.
|
||||
Network interface attachment resource schema. Attaches a network interface to a server. Must have a `region` specified in the provider configuration. The attachment takes only effect after server reboot.
|
||||
|
||||
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
|
||||
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@ func (r *serverDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
|
|||
Description: "The image ID to be used for an ephemeral disk on the server.",
|
||||
Computed: true,
|
||||
},
|
||||
"network_interfaces": schema.ListAttribute{
|
||||
Description: "The IDs of network interfaces which should be attached to the server. Updating it will recreate the server.",
|
||||
Computed: true,
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"keypair_name": schema.StringAttribute{
|
||||
Description: "The name of the keypair used during server creation.",
|
||||
Computed: true,
|
||||
|
|
@ -201,7 +206,9 @@ func (r *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
|
||||
serverResp, err := r.client.GetServer(ctx, projectId, serverId).Execute()
|
||||
serverReq := r.client.GetServer(ctx, projectId, serverId)
|
||||
serverReq = serverReq.Details(true)
|
||||
serverResp, err := serverReq.Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
|
|
@ -17,6 +18,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"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"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
|
|
@ -57,22 +59,23 @@ const (
|
|||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
ServerId types.String `tfsdk:"server_id"`
|
||||
MachineType types.String `tfsdk:"machine_type"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
AvailabilityZone types.String `tfsdk:"availability_zone"`
|
||||
BootVolume types.Object `tfsdk:"boot_volume"`
|
||||
ImageId types.String `tfsdk:"image_id"`
|
||||
KeypairName types.String `tfsdk:"keypair_name"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
AffinityGroup types.String `tfsdk:"affinity_group"`
|
||||
UserData types.String `tfsdk:"user_data"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
LaunchedAt types.String `tfsdk:"launched_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
DesiredStatus types.String `tfsdk:"desired_status"`
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
ServerId types.String `tfsdk:"server_id"`
|
||||
MachineType types.String `tfsdk:"machine_type"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
AvailabilityZone types.String `tfsdk:"availability_zone"`
|
||||
BootVolume types.Object `tfsdk:"boot_volume"`
|
||||
ImageId types.String `tfsdk:"image_id"`
|
||||
NetworkInterfaces types.List `tfsdk:"network_interfaces"`
|
||||
KeypairName types.String `tfsdk:"keypair_name"`
|
||||
Labels types.Map `tfsdk:"labels"`
|
||||
AffinityGroup types.String `tfsdk:"affinity_group"`
|
||||
UserData types.String `tfsdk:"user_data"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
LaunchedAt types.String `tfsdk:"launched_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
DesiredStatus types.String `tfsdk:"desired_status"`
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.BootVolume
|
||||
|
|
@ -286,6 +289,20 @@ func (r *serverResource) Schema(_ context.Context, _ resource.SchemaRequest, res
|
|||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"network_interfaces": schema.ListAttribute{
|
||||
Description: "The IDs of network interfaces which should be attached to the server. Updating it will recreate the server.",
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
Validators: []validator.List{
|
||||
listvalidator.ValueStringsAre(
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
),
|
||||
},
|
||||
PlanModifiers: []planmodifier.List{
|
||||
listplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"keypair_name": schema.StringAttribute{
|
||||
Description: "The name of the keypair used during server creation.",
|
||||
Optional: true,
|
||||
|
|
@ -422,13 +439,21 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
|
|||
}
|
||||
|
||||
serverId := *server.Id
|
||||
server, err = wait.CreateServerWaitHandler(ctx, r.client, projectId, serverId).WaitWithContext(ctx)
|
||||
_, err = wait.CreateServerWaitHandler(ctx, r.client, projectId, serverId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("server creation waiting: %v", err))
|
||||
return
|
||||
}
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
|
||||
// Get Server with details
|
||||
serverReq := r.client.GetServer(ctx, projectId, serverId)
|
||||
serverReq = serverReq.Details(true)
|
||||
server, err = serverReq.Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating server", fmt.Sprintf("get server details: %v", err))
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, server, &model)
|
||||
if err != nil {
|
||||
|
|
@ -574,7 +599,9 @@ func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, res
|
|||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "server_id", serverId)
|
||||
|
||||
serverResp, err := r.client.GetServer(ctx, projectId, serverId).Execute()
|
||||
serverReq := r.client.GetServer(ctx, projectId, serverId)
|
||||
serverReq = serverReq.Details(true)
|
||||
serverResp, err := serverReq.Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -815,6 +842,20 @@ func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model) error
|
|||
launchedAtValue := *serverResp.LaunchedAt
|
||||
launchedAt = types.StringValue(launchedAtValue.Format(time.RFC3339))
|
||||
}
|
||||
if serverResp.Nics != nil {
|
||||
var respNics []string
|
||||
for _, nic := range *serverResp.Nics {
|
||||
respNics = append(respNics, *nic.NicId)
|
||||
}
|
||||
nicTF, diags := types.ListValueFrom(ctx, types.StringType, respNics)
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("failed to map networkInterfaces: %w", core.DiagsToError(diags))
|
||||
}
|
||||
|
||||
model.NetworkInterfaces = nicTF
|
||||
} else {
|
||||
model.NetworkInterfaces = types.ListNull(types.StringType)
|
||||
}
|
||||
|
||||
model.ServerId = types.StringValue(serverId)
|
||||
model.MachineType = types.StringPointerValue(serverResp.MachineType)
|
||||
|
|
@ -875,6 +916,24 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
|
|||
userData = &encodedUserData
|
||||
}
|
||||
|
||||
var network *iaas.CreateServerPayloadNetworking
|
||||
if !model.NetworkInterfaces.IsNull() && !model.NetworkInterfaces.IsUnknown() {
|
||||
var nicIds []string
|
||||
for _, nic := range model.NetworkInterfaces.Elements() {
|
||||
nicString, ok := nic.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type assertion failed")
|
||||
}
|
||||
nicIds = append(nicIds, nicString.ValueString())
|
||||
}
|
||||
|
||||
network = &iaas.CreateServerPayloadNetworking{
|
||||
CreateServerNetworkingWithNics: &iaas.CreateServerNetworkingWithNics{
|
||||
NicIds: &nicIds,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &iaas.CreateServerPayload{
|
||||
AvailabilityZone: conversion.StringValueToPointer(model.AvailabilityZone),
|
||||
BootVolume: bootVolumePayload,
|
||||
|
|
@ -882,6 +941,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateServerPaylo
|
|||
KeypairName: conversion.StringValueToPointer(model.KeypairName),
|
||||
Labels: &labels,
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Networking: network,
|
||||
MachineType: conversion.StringValueToPointer(model.MachineType),
|
||||
UserData: userData,
|
||||
}, nil
|
||||
|
|
|
|||
|
|
@ -43,19 +43,20 @@ func TestMapFields(t *testing.T) {
|
|||
Id: utils.Ptr("sid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ServerId: types.StringValue("sid"),
|
||||
Name: types.StringNull(),
|
||||
AvailabilityZone: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
ImageId: types.StringNull(),
|
||||
KeypairName: types.StringNull(),
|
||||
AffinityGroup: types.StringNull(),
|
||||
UserData: types.StringNull(),
|
||||
CreatedAt: types.StringNull(),
|
||||
UpdatedAt: types.StringNull(),
|
||||
LaunchedAt: types.StringNull(),
|
||||
Id: types.StringValue("pid,sid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ServerId: types.StringValue("sid"),
|
||||
Name: types.StringNull(),
|
||||
AvailabilityZone: types.StringNull(),
|
||||
Labels: types.MapNull(types.StringType),
|
||||
ImageId: types.StringNull(),
|
||||
NetworkInterfaces: types.ListNull(types.StringType),
|
||||
KeypairName: types.StringNull(),
|
||||
AffinityGroup: types.StringNull(),
|
||||
UserData: types.StringNull(),
|
||||
CreatedAt: types.StringNull(),
|
||||
UpdatedAt: types.StringNull(),
|
||||
LaunchedAt: types.StringNull(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
|
@ -72,7 +73,15 @@ func TestMapFields(t *testing.T) {
|
|||
Labels: &map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
ImageId: utils.Ptr("image_id"),
|
||||
ImageId: utils.Ptr("image_id"),
|
||||
Nics: &[]iaas.ServerNetwork{
|
||||
{
|
||||
NicId: utils.Ptr("nic1"),
|
||||
},
|
||||
{
|
||||
NicId: utils.Ptr("nic2"),
|
||||
},
|
||||
},
|
||||
KeypairName: utils.Ptr("keypair_name"),
|
||||
AffinityGroup: utils.Ptr("group_id"),
|
||||
CreatedAt: utils.Ptr(testTimestamp()),
|
||||
|
|
@ -89,7 +98,11 @@ func TestMapFields(t *testing.T) {
|
|||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
|
||||
"key": types.StringValue("value"),
|
||||
}),
|
||||
ImageId: types.StringValue("image_id"),
|
||||
ImageId: types.StringValue("image_id"),
|
||||
NetworkInterfaces: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("nic1"),
|
||||
types.StringValue("nic2"),
|
||||
}),
|
||||
KeypairName: types.StringValue("keypair_name"),
|
||||
AffinityGroup: types.StringValue("group_id"),
|
||||
CreatedAt: types.StringValue(testTimestampValue),
|
||||
|
|
@ -109,19 +122,20 @@ func TestMapFields(t *testing.T) {
|
|||
Id: utils.Ptr("sid"),
|
||||
},
|
||||
Model{
|
||||
Id: types.StringValue("pid,sid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ServerId: types.StringValue("sid"),
|
||||
Name: types.StringNull(),
|
||||
AvailabilityZone: types.StringNull(),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
ImageId: types.StringNull(),
|
||||
KeypairName: types.StringNull(),
|
||||
AffinityGroup: types.StringNull(),
|
||||
UserData: types.StringNull(),
|
||||
CreatedAt: types.StringNull(),
|
||||
UpdatedAt: types.StringNull(),
|
||||
LaunchedAt: types.StringNull(),
|
||||
Id: types.StringValue("pid,sid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
ServerId: types.StringValue("sid"),
|
||||
Name: types.StringNull(),
|
||||
AvailabilityZone: types.StringNull(),
|
||||
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
|
||||
ImageId: types.StringNull(),
|
||||
NetworkInterfaces: types.ListNull(types.StringType),
|
||||
KeypairName: types.StringNull(),
|
||||
AffinityGroup: types.StringNull(),
|
||||
UserData: types.StringNull(),
|
||||
CreatedAt: types.StringNull(),
|
||||
UpdatedAt: types.StringNull(),
|
||||
LaunchedAt: types.StringNull(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue