fix: state drift of "stackit_server" (#679)

* fix: State drift of "stackit_server" when using "stackit_server_network_interface_attach"

* fix: tests

* add acceptance tests for stackit_server_network_interface_attach
This commit is contained in:
Marcel Jacek 2025-02-18 14:33:39 +01:00 committed by GitHub
parent 3e8dcc542b
commit b7f56d1685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 172 additions and 22 deletions

View file

@ -19,6 +19,7 @@ import (
const (
serverMachineType = "t1.1"
updatedServerMachineType = "t1.2"
nicAttachTfName = "second_network_interface"
)
// Network resource data
@ -53,6 +54,7 @@ var networkInterfaceResource = map[string]string{
"project_id": testutil.ProjectId,
"network_id": networkResource["network_id"],
"name": "name",
"tfName": "network_interface",
}
// Volume resource data
@ -200,14 +202,15 @@ func networkAreaRouteResourceConfig(labelValue string) string {
)
}
func networkInterfaceResourceConfig(name string) string {
func networkInterfaceResourceConfig(resourceName, name string) string {
return fmt.Sprintf(`
resource "stackit_network_interface" "network_interface" {
resource "stackit_network_interface" "%s" {
project_id = stackit_network.network.project_id
network_id = stackit_network.network.network_id
name = "%s"
}
`,
resourceName,
name,
)
}
@ -355,6 +358,19 @@ func imageResourceConfig(name string) string {
)
}
func networkInterfaceAttachmentResourceConfig(nicTfName string) string {
return fmt.Sprintf(`
resource "stackit_server_network_interface_attach" "attach_nic" {
project_id = "%s"
server_id = stackit_server.server.server_id
network_interface_id = stackit_network_interface.%s.network_interface_id
}
`,
testutil.ProjectId,
nicTfName,
)
}
func testAccNetworkAreaConfig(areaname, networkranges, routeLabelValue string) string {
return fmt.Sprintf("%s\n\n%s\n\n%s",
testutil.IaaSProviderConfig(),
@ -370,13 +386,15 @@ func testAccVolumeConfig(name, size string) string {
)
}
func testAccServerConfig(name, nameservers, serverName, machineType, interfacename string) string {
return fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s",
func testAccServerConfig(name, nameservers, serverName, machineType, nicTfName, interfacename string) string {
return fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s",
testutil.IaaSProviderConfig(),
networkResourceConfig(name, nameservers),
serverResourceConfig(serverName, machineType),
volumeResourceConfig(volumeResource["name"], volumeResource["size"]),
networkInterfaceResourceConfig(interfacename),
networkInterfaceResourceConfig(nicTfName, interfacename),
networkInterfaceResourceConfig(nicAttachTfName, fmt.Sprintf("%s-%s", interfacename, nicAttachTfName)),
networkInterfaceAttachmentResourceConfig(nicAttachTfName),
volumeAttachmentResourceConfig(),
serviceAccountAttachmentResourceConfig(),
)
@ -390,11 +408,11 @@ func resourceConfigSecurityGroup(name, direction string) string {
)
}
func testAccPublicIpConfig(nameNetwork, nameservers, nameNetworkInterface, publicIpResourceConfig string) string {
func testAccPublicIpConfig(nameNetwork, nameservers, nicTfName, nameNetworkInterface, publicIpResourceConfig string) string {
return fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s",
testutil.IaaSProviderConfig(),
networkResourceConfigRouted(nameNetwork, nameservers),
networkInterfaceResourceConfig(nameNetworkInterface),
networkInterfaceResourceConfig(nicTfName, nameNetworkInterface),
publicIpResourceConfig,
)
}
@ -659,6 +677,7 @@ func TestAccVolume(t *testing.T) {
}
func TestAccServer(t *testing.T) {
networkInterfaceSecSchemaName := fmt.Sprintf("stackit_network_interface.%s", nicAttachTfName)
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckServerDestroy,
@ -674,6 +693,7 @@ func TestAccServer(t *testing.T) {
),
serverResource["name"],
serverResource["machine_type"],
networkInterfaceResource["tfName"],
networkInterfaceResource["name"],
),
Check: resource.ComposeAggregateTestCheckFunc(
@ -697,6 +717,9 @@ func TestAccServer(t *testing.T) {
resource.TestCheckResourceAttr("stackit_server.server", "labels.label1", serverResource["label1"]),
resource.TestCheckResourceAttr("stackit_server.server", "user_data", serverResource["user_data"]),
resource.TestCheckResourceAttrSet("stackit_server.server", "network_interfaces.0"),
// 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.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"]),
@ -714,6 +737,35 @@ 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"]),
// Network Interface second
resource.TestCheckResourceAttrPair(
networkInterfaceSecSchemaName, "project_id",
"stackit_network.network", "project_id",
),
resource.TestCheckResourceAttrPair(
networkInterfaceSecSchemaName, "network_id",
"stackit_network.network", "network_id",
),
resource.TestCheckResourceAttrSet(networkInterfaceSecSchemaName, "network_interface_id"),
resource.TestCheckResourceAttr(
networkInterfaceSecSchemaName, "name",
fmt.Sprintf("%s-%s", networkInterfaceResource["name"], nicAttachTfName),
),
// Network Interface Attachment
resource.TestCheckResourceAttrPair(
"stackit_server_network_interface_attach.attach_nic", "project_id",
"stackit_network.network", "project_id",
),
resource.TestCheckResourceAttrPair(
"stackit_server_network_interface_attach.attach_nic", "server_id",
"stackit_server.server", "server_id",
),
resource.TestCheckResourceAttrPair(
"stackit_server_network_interface_attach.attach_nic", "network_interface_id",
networkInterfaceSecSchemaName, "network_interface_id",
),
// Volume attachment
resource.TestCheckResourceAttrPair(
"stackit_server_volume_attach.attach_volume", "project_id",
@ -768,6 +820,7 @@ func TestAccServer(t *testing.T) {
),
serverResource["name"],
serverResource["machine_type"],
networkInterfaceResource["tfName"],
networkInterfaceResource["name"],
),
),
@ -840,7 +893,7 @@ func TestAccServer(t *testing.T) {
},
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"boot_volume", "user_data"}, // Field is not mapped as it is only relevant on creation
ImportStateVerifyIgnore: []string{"boot_volume", "user_data", "network_interfaces"}, // Field is not mapped as it is only relevant on creation
},
{
ResourceName: "stackit_network_interface.network_interface",
@ -862,6 +915,46 @@ func TestAccServer(t *testing.T) {
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: networkInterfaceSecSchemaName,
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources[networkInterfaceSecSchemaName]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_network_interface.%s", nicAttachTfName)
}
networkId, ok := r.Primary.Attributes["network_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_id")
}
networkInterfaceId, ok := r.Primary.Attributes["network_interface_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_interface_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, networkId, networkInterfaceId), nil
},
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "stackit_server_network_interface_attach.attach_nic",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_server_network_interface_attach.attach_nic"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_network_interface.%s", nicAttachTfName)
}
serverId, ok := r.Primary.Attributes["server_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_id")
}
networkInterfaceId, ok := r.Primary.Attributes["network_interface_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_interface_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, serverId, networkInterfaceId), nil
},
ImportState: true,
ImportStateVerify: false,
},
{
ResourceName: "stackit_server_volume_attach.attach_volume",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
@ -913,6 +1006,7 @@ func TestAccServer(t *testing.T) {
),
fmt.Sprintf("%s-updated", serverResource["name"]),
updatedServerMachineType,
networkInterfaceResource["tfName"],
fmt.Sprintf("%s-updated", networkInterfaceResource["name"]),
),
Check: resource.ComposeAggregateTestCheckFunc(
@ -950,6 +1044,35 @@ func TestAccServer(t *testing.T) {
),
resource.TestCheckResourceAttrSet("stackit_network_interface.network_interface", "network_interface_id"),
resource.TestCheckResourceAttr("stackit_network_interface.network_interface", "name", fmt.Sprintf("%s-updated", networkInterfaceResource["name"])),
// Network Interface second
resource.TestCheckResourceAttrPair(
networkInterfaceSecSchemaName, "project_id",
"stackit_network.network", "project_id",
),
resource.TestCheckResourceAttrPair(
networkInterfaceSecSchemaName, "network_id",
"stackit_network.network", "network_id",
),
resource.TestCheckResourceAttrSet(networkInterfaceSecSchemaName, "network_interface_id"),
resource.TestCheckResourceAttr(
networkInterfaceSecSchemaName, "name",
fmt.Sprintf("%s-%s", fmt.Sprintf("%s-updated", networkInterfaceResource["name"]), nicAttachTfName),
),
// Network Interface Attachment
resource.TestCheckResourceAttrPair(
"stackit_server_network_interface_attach.attach_nic", "project_id",
networkInterfaceSecSchemaName, "project_id",
),
resource.TestCheckResourceAttrPair(
"stackit_server_network_interface_attach.attach_nic", "server_id",
"stackit_server.server", "server_id",
),
resource.TestCheckResourceAttrPair(
"stackit_server_network_interface_attach.attach_nic", "network_interface_id",
networkInterfaceSecSchemaName, "network_interface_id",
),
),
},
// Deletion is done by the framework implicitly
@ -1107,6 +1230,7 @@ func TestAccPublicIp(t *testing.T) {
"[%q]",
networkResource["nameserver0"],
),
networkInterfaceResource["tfName"],
networkInterfaceResource["name"],
fmt.Sprintf(`
resource "stackit_public_ip" "public_ip" {
@ -1146,6 +1270,7 @@ func TestAccPublicIp(t *testing.T) {
"[%q]",
networkResource["nameserver0"],
),
networkInterfaceResource["tfName"],
networkInterfaceResource["name"],
fmt.Sprintf(`
resource "stackit_public_ip" "public_ip" {
@ -1198,6 +1323,7 @@ func TestAccPublicIp(t *testing.T) {
"[%q]",
networkResource["nameserver0"],
),
networkInterfaceResource["tfName"],
networkInterfaceResource["name"],
fmt.Sprintf(`
resource "stackit_public_ip" "public_ip" {

View file

@ -888,12 +888,39 @@ func mapFields(ctx context.Context, serverResp *iaas.Server, model *Model) error
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))
var modelNics []string
for _, modelNic := range model.NetworkInterfaces.Elements() {
modelNicString, ok := modelNic.(types.String)
if !ok {
return fmt.Errorf("type assertion for network interfaces failed")
}
modelNics = append(modelNics, modelNicString.ValueString())
}
model.NetworkInterfaces = nicTF
var filteredNics []string
for _, modelNic := range modelNics {
for _, nic := range respNics {
if nic == modelNic {
filteredNics = append(filteredNics, nic)
break
}
}
}
// Sorts the filteredNics based on the modelNics order
resultNics := utils.ReconcileStringSlices(modelNics, filteredNics)
if len(resultNics) != 0 {
nicTF, diags := types.ListValueFrom(ctx, types.StringType, resultNics)
if diags.HasError() {
return fmt.Errorf("failed to map networkInterfaces: %w", core.DiagsToError(diags))
}
model.NetworkInterfaces = nicTF
} else {
model.NetworkInterfaces = types.ListNull(types.StringType)
}
} else {
model.NetworkInterfaces = types.ListNull(types.StringType)
}

View file

@ -98,16 +98,13 @@ func TestMapFields(t *testing.T) {
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
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),
UpdatedAt: types.StringValue(testTimestampValue),
LaunchedAt: types.StringValue(testTimestampValue),
ImageId: types.StringValue("image_id"),
NetworkInterfaces: types.ListNull(types.StringType),
KeypairName: types.StringValue("keypair_name"),
AffinityGroup: types.StringValue("group_id"),
CreatedAt: types.StringValue(testTimestampValue),
UpdatedAt: types.StringValue(testTimestampValue),
LaunchedAt: types.StringValue(testTimestampValue),
},
true,
},