diff --git a/docs/resources/compute_interface_attach_v2.md b/docs/resources/compute_interface_attach_v2.md index 67999596..d3d79b7b 100644 --- a/docs/resources/compute_interface_attach_v2.md +++ b/docs/resources/compute_interface_attach_v2.md @@ -6,105 +6,70 @@ page_title: "flexibleengine_compute_interface_attach_v2" # flexibleengine_compute_interface_attach_v2 -Attaches a Network Interface (a Port) to an Instance using the FlexibleEngine -Compute (Nova) v2 API. +Attaches a Network Interface (a Port) to an Instance using the FlexibleEngine Compute (Nova) v2 API. ## Example Usage -### Basic Attachment +### Attach a port (under the specified network) to the ECS instance and generate a random IP address ```hcl -resource "flexibleengine_vpc_v1" "example_vpc" { - name = "example-vpc" - cidr = "192.168.0.0/16" -} - -resource "flexibleengine_vpc_subnet_v1" "example_subnet" { - name = "example-vpc-subnet" - cidr = "192.168.0.0/24" - gateway_ip = "192.168.0.1" - vpc_id = flexibleengine_vpc_v1.example_vpc.id -} - -resource "flexibleengine_compute_instance_v2" "example_instance" { - name = "example-instance" - security_groups = ["default"] -} - -resource "flexibleengine_compute_interface_attach_v2" "example_interface_attach" { - instance_id = flexibleengine_compute_instance_v2.example_instance.id - network_id = flexibleengine_vpc_subnet_v1.example_subnet.id -} - -``` +variable "instance_id" {} +variable "network_id" {} -### Attachment Specifying a Fixed IP - -```hcl -resource "flexibleengine_compute_interface_attach_v2" "example_interface_attach" { - instance_id = flexibleengine_compute_instance_v2.example_instance.id - network_id = flexibleengine_vpc_subnet_v1.example_subnet.id - fixed_ip = "10.0.10.10" +resource "flexibleengine_compute_interface_attach_v2" "test" { + instance_id = var.instance_id + network_id = var.network_id } - ``` -### Attachment Using an Existing Port +### Attach a port (under the specified network) to the ECS instance and use the custom security groups ```hcl -resource "flexibleengine_networking_port_v2" "example_port" { - name = "port_1" - network_id = flexibleengine_vpc_subnet_v1.example_subnet.id - admin_state_up = "true" +variable "instance_id" {} +variable "network_id" {} +variable "security_group_ids" { + type = list(string) } -resource "flexibleengine_compute_interface_attach_v2" "example_interface_attach" { - instance_id = flexibleengine_compute_instance_v2.example_instance.id - port_id = flexibleengine_networking_port_v2.example_port.id +resource "flexibleengine_compute_interface_attach_v2" "test" { + instance_id = var.instance_id + network_id = var.network_id + fixed_ip = "192.168.10.199" + security_group_ids = var.security_group_ids } ``` -### Attaching Multiple Interfaces +### Attach a custom port to the ECS instance ```hcl -resource "flexibleengine_networking_port_v2" "example_ports" { - count = 2 - name = format("port-%02d", count.index + 1) - network_id = flexibleengine_vpc_subnet_v1.example_subnet.id - admin_state_up = "true" -} +variable "security_group_id" {} -resource "flexibleengine_compute_interface_attach_v2" "example_attachments" { - count = 2 - instance_id = flexibleengine_compute_instance_v2.example_instance.id - port_id = flexibleengine_networking_port_v2.example_ports.*.id[count.index] +data "flexibleengine_vpc_subnet_v1" "mynet" { + name = "subnet-default" } -``` - -Note that the above example will not guarantee that the ports are attached in -a deterministic manner. The ports will be attached in a seemingly random -order. -If you want to ensure that the ports are attached in a given order, create -explicit dependencies between the ports, such as: - -```hcl -resource "flexibleengine_networking_port_v2" "example_ports" { - count = 2 - name = format("port-%02d", count.index + 1) - network_id = flexibleengine_vpc_subnet_v1.example_subnet.id - admin_state_up = "true" +data "flexibleengine_networking_port" "myport" { + network_id = data.flexibleengine_vpc_subnet_v1.mynet.id + fixed_ip = "192.168.0.100" } -resource "flexibleengine_compute_interface_attach_v2" "example_interface_attach_1" { - instance_id = flexibleengine_compute_instance_v2.instance_1.id - port_id = flexibleengine_networking_port_v2.example_ports.*.id[0] +resource "flexibleengine_compute_instance_v2" "myinstance" { + name = "instance" + image_id = "ad091b52-742f-469e-8f3c-fd81cadf0743" + flavor_id = "s3.large.2" + key_pair = "my_key_pair_name" + security_groups = [flexibleengine_networking_secgroup_v2.test.name] + availability_zone = "eu-west-0a" + + network { + uuid = flexibleengine_vpc_subnet_v1.mynet.id + } } -resource "flexibleengine_compute_interface_attach_v2" "example_interface_attach_2" { - instance_id = flexibleengine_compute_instance_v2.instance_1.id - port_id = flexibleengine_networking_port_v2.example_ports.*.id[1] +resource "flexibleengine_compute_interface_attach_v2" "attached" { + instance_id = flexibleengine_compute_instance_v2.myinstance.id + port_id = data.flexibleengine_networking_port.myport.id } ``` @@ -112,24 +77,35 @@ resource "flexibleengine_compute_interface_attach_v2" "example_interface_attach_ The following arguments are supported: -* `region` - (Optional, String, ForceNew) The region in which to create the interface attachment. - If omitted, the `region` argument of the provider is used. Changing this creates a new attachment. +* `region` - (Optional, String, ForceNew) The region in which to create the network interface attache resource. If + omitted, the provider-level region will be used. Changing this creates a new network interface attache resource. * `instance_id` - (Required, String, ForceNew) The ID of the Instance to attach the Port or Network to. -* `port_id` - (Optional, String, ForceNew) The ID of the Port to attach to an Instance. - This option and `network_id` are mutually exclusive. +* `port_id` - (Optional, String, ForceNew) The ID of the Port to attach to an Instance. This option and `network_id` are + mutually exclusive. -* `network_id` - (Optional, String, ForceNew) The ID of the Network to attach to an Instance. - A port will be created automatically. This option and `port_id` are mutually exclusive. +* `network_id` - (Optional, String, ForceNew) The ID of the Network to attach to an Instance. A port will be created + automatically. This option and `port_id` are mutually exclusive. * `fixed_ip` - (Optional, String, ForceNew) An IP address to associate with the port. - This option cannot be used with port_id. You must specify a network_id. - The IP address must lie in a range on the supplied network. + + ->This option cannot be used with port_id. You must specify a network_id. The IP address must lie in a range on the + supplied network. + +* `source_dest_check` - (Optional, Bool) Specifies whether the ECS processes only traffic that is destined specifically + for it. This function is enabled by default but should be disabled if the ECS functions as a SNAT server or has a + virtual IP address bound to it. + +* `security_group_ids` - (Optional, List) Specifies the list of security group IDs bound to the specified port. + Defaults to the default security group. ## Attribute Reference -All the arguments above can also be exported attributes. +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID in format of ECS instance ID and port ID separated by a slash. +* `mac` - The MAC address of the NIC. ## Timeouts @@ -140,9 +116,10 @@ This resource provides the following timeouts configuration options: ## Import -Interface Attachments can be imported using the Instance ID and Port ID -separated by a slash, e.g. +Interface Attachments can be imported using the Instance ID and Port ID separated by a slash, e.g. ```shell -terraform import flexibleengine_compute_interface_attach_v2.ai_1 89c60255-9bd6-460c-822a-e2b959ede9d2/45670584-225f-46c3-b33e-6707b589b666 +$ terraform import flexibleengine_compute_interface_attach_v2.ai_1 89c60255-9bd6-460c-822a-e2b959ede9d2/45670584-225f-46c3-b33e-6707b589b666 +``` + ``` diff --git a/flexibleengine/acceptance/resource_flexibleengine_compute_interface_attach_v2_test.go b/flexibleengine/acceptance/resource_flexibleengine_compute_interface_attach_v2_test.go new file mode 100644 index 00000000..f3ec9b68 --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_compute_interface_attach_v2_test.go @@ -0,0 +1,197 @@ +package acceptance + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk/openstack/compute/v2/extensions/attachinterfaces" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func TestAccComputeInterfaceAttach_Basic(t *testing.T) { + var ai attachinterfaces.Interface + rName := acceptance.RandomAccResourceNameWithDash() + resourceName := "flexibleengine_compute_interface_attach_v2.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: testAccCheckComputeInterfaceAttachDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInterfaceAttach_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInterfaceAttachExists(resourceName, &ai), + testAccCheckComputeInterfaceAttachIP(&ai, "192.168.0.199"), + resource.TestCheckResourceAttr(resourceName, "source_dest_check", "true"), + resource.TestCheckResourceAttrPair(resourceName, "security_group_ids.0", + "flexibleengine_networking_secgroup_v2.test", "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func computeInterfaceAttachParseID(id string) (instanceID, portID string, err error) { + idParts := strings.Split(id, "/") + if len(idParts) < 2 { + err = fmt.Errorf("unable to parse the resource ID, must be / format") + return + } + + instanceID = idParts[0] + portID = idParts[1] + return +} + +func testAccCheckComputeInterfaceAttachDestroy(s *terraform.State) error { + cfg := acceptance.TestAccProvider.Meta().(*config.Config) + computeClient, err := cfg.ComputeV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("error creating compute client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "flexibleengine_compute_interface_attach_v2" { + continue + } + + instanceId, portId, err := computeInterfaceAttachParseID(rs.Primary.ID) + if err != nil { + return err + } + + _, err = attachinterfaces.Get(computeClient, instanceId, portId).Extract() + if err == nil { + return fmt.Errorf("interface attachment still exists") + } + } + + return nil +} + +func testAccCheckComputeInterfaceAttachExists(n string, ai *attachinterfaces.Interface) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no ID is set") + } + + cfg := acceptance.TestAccProvider.Meta().(*config.Config) + computeClient, err := cfg.ComputeV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("error creating compute client: %s", err) + } + + instanceId, portId, err := computeInterfaceAttachParseID(rs.Primary.ID) + if err != nil { + return err + } + + found, err := attachinterfaces.Get(computeClient, instanceId, portId).Extract() + if err != nil { + return err + } + if found.PortID != portId { + return fmt.Errorf("interface attachment not found") + } + + *ai = *found + + return nil + } +} + +func testAccCheckComputeInterfaceAttachIP( + ai *attachinterfaces.Interface, ip string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range ai.FixedIPs { + if i.IPAddress == ip { + return nil + } + } + return fmt.Errorf("requested ip (%s) does not exist on port", ip) + } +} + +func testAccComputeInterfaceAttach_basic(rName string) string { + return fmt.Sprintf(` +data "flexibleengine_availability_zones" "test" {} + +resource "flexibleengine_vpc_v1" "test" { + name = "%[1]s" + cidr = "192.168.0.0/16" +} + +resource "flexibleengine_vpc_subnet_v1" "test" { + vpc_id = flexibleengine_vpc_v1.test.id + name = "%[1]s" + cidr = cidrsubnet(flexibleengine_vpc_v1.test.cidr, 4, 0) + gateway_ip = cidrhost(cidrsubnet(flexibleengine_vpc_v1.test.cidr, 4, 0), 1) +} + +resource "flexibleengine_networking_secgroup_v2" "test" { + name = "%[1]s" +} + +resource "flexibleengine_apig_instance" "test" { + name = "%[1]s" + edition = "BASIC" + vpc_id = flexibleengine_vpc_v1.test.id + subnet_id = flexibleengine_vpc_subnet_v1.test.id + security_group_id = flexibleengine_networking_secgroup_v2.test.id + enterprise_project_id = "0" + + availability_zones = try(slice(data.flexibleengine_availability_zones.test.names, 0, 1), null) +} + +data "flexibleengine_compute_flavors_v2" "test" { + availability_zone = data.flexibleengine_availability_zones.test.names[0] + performance_type = "normal" + cpu_core = 2 + memory_size = 4 +} + +data "flexibleengine_images_images" "test" { + flavor_id = data.flexibleengine_compute_flavors_v2.test.flavors[0] + + os = "Ubuntu" + visibility = "public" +} + +resource "flexibleengine_compute_instance_v2" "test" { + name = "%[1]s" + image_id = data.flexibleengine_images_images.test.images[0].id + flavor_id = data.flexibleengine_compute_flavors_v2.test.flavors[0] + availability_zone = data.flexibleengine_availability_zones.test.names[0] + + security_groups = [flexibleengine_networking_secgroup_v2.test.name] + + network { + uuid = flexibleengine_vpc_subnet_v1.test.id + } +} + +resource "flexibleengine_compute_interface_attach_v2" "test" { + instance_id = flexibleengine_compute_instance_v2.test.id + network_id = flexibleengine_vpc_subnet_v1.test.id + fixed_ip = cidrhost(cidrsubnet(flexibleengine_vpc_v1.test.cidr, 4, 0), 199) + security_group_ids = [flexibleengine_networking_secgroup_v2.test.id] +} +`, rName) +} diff --git a/flexibleengine/provider.go b/flexibleengine/provider.go index 5a861c87..2cf0dd46 100644 --- a/flexibleengine/provider.go +++ b/flexibleengine/provider.go @@ -409,7 +409,7 @@ func Provider() *schema.Provider { "flexibleengine_blockstorage_volume_v2": resourceBlockStorageVolumeV2(), "flexibleengine_compute_instance_v2": resourceComputeInstanceV2(), - "flexibleengine_compute_interface_attach_v2": resourceComputeInterfaceAttachV2(), + "flexibleengine_compute_interface_attach_v2": ecs.ResourceComputeInterfaceAttach(), "flexibleengine_compute_keypair_v2": resourceComputeKeypairV2(), "flexibleengine_compute_servergroup_v2": resourceComputeServerGroupV2(), "flexibleengine_compute_floatingip_v2": resourceComputeFloatingIPV2(),