From 3b40b65cfda86b4159226128de1a7efa009f78ce Mon Sep 17 00:00:00 2001 From: ShiChangkuo Date: Mon, 28 Dec 2020 17:11:42 +0800 Subject: [PATCH] add vpc endpoint approval resource and docs --- docs/resources/vpcep_approval.md | 75 +++++++ huaweicloud/provider.go | 1 + .../resource_huaweicloud_vpcep_approval.go | 212 ++++++++++++++++++ ...esource_huaweicloud_vpcep_approval_test.go | 126 +++++++++++ 4 files changed, 414 insertions(+) create mode 100644 docs/resources/vpcep_approval.md create mode 100644 huaweicloud/resource_huaweicloud_vpcep_approval.go create mode 100644 huaweicloud/resource_huaweicloud_vpcep_approval_test.go diff --git a/docs/resources/vpcep_approval.md b/docs/resources/vpcep_approval.md new file mode 100644 index 0000000000..4d1c173358 --- /dev/null +++ b/docs/resources/vpcep_approval.md @@ -0,0 +1,75 @@ +--- +subcategory: "VPC Endpoint (VPCEP)" +--- + +# huaweicloud\_vpcep\_approval + +Provides a resource to manage the VPC endpoint connections. + +## Example Usage + +```hcl +variable "service_vpc_id" {} +variable "vm_port" {} +variable "vpc_id" {} +variable "network_id" {} + +resource "huaweicloud_vpcep_service" "demo" { + name = "demo-service" + server_type = "VM" + vpc_id = var.service_vpc_id + port_id = var.vm_port + approval = true + + port_mapping { + service_port = 8080 + terminal_port = 80 + } +} + +resource "huaweicloud_vpcep_endpoint" "demo" { + service_id = huaweicloud_vpcep_service.demo.id + vpc_id = var.vpc_id + network_id = var.network_id + enable_dns = true + + lifecycle { + # enable_dns and ip_address are not assigned until connecting to the service + ignore_changes = [enable_dns, ip_address] + } +} + +resource "huaweicloud_vpcep_approval" "approval" { + service_id = huaweicloud_vpcep_service.demo.id + endpoints = [huaweicloud_vpcep_endpoint.demo.id] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) The region in which to obtain the VPC endpoint service. + If omitted, the provider-level region will be used. Changing this creates a new resource. + +* `service_id` (Optional, String, ForceNew) - Specifies the ID of the VPC endpoint service. Changing this creates a new resource. + +* `endpoints` (Optional, List) - Specifies the list of VPC endpoint IDs which accepted to connect to VPC endpoint service. + The VPC endpoints will be rejected when the resource was destroyed. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique ID in UUID format which equals to the ID of the VPC endpoint service. + +* `connections` - An array of VPC endpoints connect to the VPC endpoint service. Structure is documented below. + - `endpoint_id` - The unique ID of the VPC endpoint. + - `marker_id` - The packet ID of the VPC endpoint. + - `domain_id` - The user's domain ID. + - `status` - The connection status of the VPC endpoint. + +## Timeouts +This resource provides the following timeouts configuration options: +- `create` - Default is 10 minute. +- `delete` - Default is 3 minute. diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index 650c5a054e..0f086bc729 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -442,6 +442,7 @@ func Provider() terraform.ResourceProvider { "huaweicloud_vpc_peering_connection_accepter": resourceVpcPeeringConnectionAccepterV2(), "huaweicloud_vpc_route": ResourceVPCRouteV2(), "huaweicloud_vpc_subnet": ResourceVpcSubnetV1(), + "huaweicloud_vpcep_approval": ResourceVPCEndpointApproval(), "huaweicloud_vpcep_endpoint": ResourceVPCEndpoint(), "huaweicloud_vpcep_service": ResourceVPCEndpointService(), "huaweicloud_vpnaas_endpoint_group": resourceVpnEndpointGroupV2(), diff --git a/huaweicloud/resource_huaweicloud_vpcep_approval.go b/huaweicloud/resource_huaweicloud_vpcep_approval.go new file mode 100644 index 0000000000..1def85cf55 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_vpcep_approval.go @@ -0,0 +1,212 @@ +package huaweicloud + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/vpcep/v1/services" +) + +const ( + actionReceive string = "receive" + actionReject string = "reject" +) + +var approvalActionStatusMap = map[string]string{ + actionReceive: "accepted", + actionReject: "rejected", +} + +func ResourceVPCEndpointApproval() *schema.Resource { + return &schema.Resource{ + Create: resourceVPCEndpointApprovalCreate, + Read: resourceVPCEndpointApprovalRead, + Update: resourceVPCEndpointApprovalUpdate, + Delete: resourceVPCEndpointApprovalDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(3 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "service_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "endpoints": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "connections": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint_id": { + Type: schema.TypeString, + Computed: true, + }, + "marker_id": { + Type: schema.TypeInt, + Computed: true, + }, + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func resourceVPCEndpointApprovalCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + vpcepClient, err := config.VPCEPClient(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating Huaweicloud VPC endpoint client: %s", err) + } + + // check status of the VPC endpoint service + serviceID := d.Get("service_id").(string) + n, err := services.Get(vpcepClient, serviceID).Extract() + if err != nil { + return fmt.Errorf("Error retrieving VPC endpoint service %s: %s", serviceID, err) + } + if n.Status != "available" { + return fmt.Errorf("Error the status of VPC endpoint service is %s, expected to be available", n.Status) + } + + raw := d.Get("endpoints").(*schema.Set).List() + err = doConnectionAction(d, vpcepClient, serviceID, actionReceive, raw) + if err != nil { + return fmt.Errorf("Error receiving connections to VPC endpoint service %s: %s", serviceID, err) + } + + d.SetId(serviceID) + return resourceVPCEndpointApprovalRead(d, meta) +} + +func resourceVPCEndpointApprovalRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + vpcepClient, err := config.VPCEPClient(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating Huaweicloud VPC endpoint client: %s", err) + } + + serviceID := d.Get("service_id").(string) + if conns, err := flattenVPCEndpointConnections(vpcepClient, serviceID); err == nil { + d.Set("connections", conns) + } + + return nil +} + +func resourceVPCEndpointApprovalUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + vpcepClient, err := config.VPCEPClient(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating Huaweicloud VPC endpoint client: %s", err) + } + + if d.HasChange("endpoints") { + old, new := d.GetChange("endpoints") + oldConnSet := old.(*schema.Set) + newConnSet := new.(*schema.Set) + received := newConnSet.Difference(oldConnSet) + rejected := oldConnSet.Difference(newConnSet) + + serviceID := d.Get("service_id").(string) + err = doConnectionAction(d, vpcepClient, serviceID, actionReceive, received.List()) + if err != nil { + return fmt.Errorf("Error receiving connections to VPC endpoint service %s: %s", serviceID, err) + } + + err = doConnectionAction(d, vpcepClient, serviceID, actionReject, rejected.List()) + if err != nil { + return fmt.Errorf("Error rejecting connections to VPC endpoint service %s: %s", serviceID, err) + } + } + return resourceVPCEndpointApprovalRead(d, meta) +} + +func resourceVPCEndpointApprovalDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + vpcepClient, err := config.VPCEPClient(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating Huaweicloud VPC endpoint client: %s", err) + } + + serviceID := d.Get("service_id").(string) + raw := d.Get("endpoints").(*schema.Set).List() + err = doConnectionAction(d, vpcepClient, serviceID, actionReject, raw) + if err != nil { + return fmt.Errorf("Error rejecting connections to VPC endpoint service %s: %s", serviceID, err) + } + + d.SetId("") + return nil +} + +func doConnectionAction(d *schema.ResourceData, client *golangsdk.ServiceClient, serviceID, action string, raw []interface{}) error { + if len(raw) == 0 { + return nil + } + + if _, ok := approvalActionStatusMap[action]; !ok { + return fmt.Errorf("approval action(%s) is invalid, only support %s or %s", action, actionReceive, actionReject) + } + + targetStatus := approvalActionStatusMap[action] + for _, v := range raw { + // Each request accepts or rejects only one VPC endpoint + epID := v.(string) + connOpts := services.ConnActionOpts{ + Action: action, + Endpoints: []string{epID}, + } + + log.Printf("[DEBUG] %s to endpoint %s from VPC endpoint service %s", action, epID, serviceID) + if result := services.ConnAction(client, serviceID, connOpts); result.Err != nil { + return result.Err + } + + log.Printf("[INFO] Waiting for VPC endpoint(%s) to become %s", epID, targetStatus) + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating", "pendingAcceptance"}, + Target: []string{targetStatus}, + Refresh: waitForVPCEndpointStatus(client, epID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 3 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, stateErr := stateConf.WaitForState() + if stateErr != nil { + return fmt.Errorf( + "Error waiting for VPC endpoint(%s) to become %s: %s", + epID, targetStatus, stateErr) + } + } + + return nil +} diff --git a/huaweicloud/resource_huaweicloud_vpcep_approval_test.go b/huaweicloud/resource_huaweicloud_vpcep_approval_test.go new file mode 100644 index 0000000000..f0996aaed5 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_vpcep_approval_test.go @@ -0,0 +1,126 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/huaweicloud/golangsdk/openstack/vpcep/v1/endpoints" + "github.com/huaweicloud/golangsdk/openstack/vpcep/v1/services" +) + +func TestAccVPCEndpointApproval(t *testing.T) { + var service services.Service + var endpoint endpoints.Endpoint + + rName := fmt.Sprintf("acc-test-%s", acctest.RandString(4)) + resourceName := "huaweicloud_vpcep_approval.approval" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVPCEPServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCEndpointApprovalBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckVPCEPServiceExists("huaweicloud_vpcep_service.test", &service), + testAccCheckVPCEndpointExists("huaweicloud_vpcep_endpoint.test", &endpoint), + resource.TestCheckResourceAttrPtr(resourceName, "id", &service.ID), + resource.TestCheckResourceAttrPtr(resourceName, "connections.0.endpoint_id", &endpoint.ID), + resource.TestCheckResourceAttr(resourceName, "connections.0.status", "accepted"), + ), + }, + { + Config: testAccVPCEndpointApprovalUpdate(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPtr(resourceName, "connections.0.endpoint_id", &endpoint.ID), + resource.TestCheckResourceAttr(resourceName, "connections.0.status", "rejected"), + ), + }, + }, + }) +} + +func testAccVPCEndpointApprovalBasic(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_vpcep_service" "test" { + name = "%s" + server_type = "VM" + vpc_id = data.huaweicloud_vpc.myvpc.id + port_id = huaweicloud_compute_instance.ecs.network[0].port + approval = true + + port_mapping { + service_port = 8080 + terminal_port = 80 + } + tags = { + owner = "tf-acc" + } +} + +resource "huaweicloud_vpcep_endpoint" "test" { + service_id = huaweicloud_vpcep_service.test.id + vpc_id = data.huaweicloud_vpc.myvpc.id + network_id = data.huaweicloud_vpc_subnet.test.id + enable_dns = true + + tags = { + owner = "tf-acc" + } + lifecycle { + ignore_changes = [enable_dns] + } +} + +resource "huaweicloud_vpcep_approval" "approval" { + service_id = huaweicloud_vpcep_service.test.id + endpoints = [huaweicloud_vpcep_endpoint.test.id] +} +`, testAccVPCEndpointPrecondition(rName), rName) +} + +func testAccVPCEndpointApprovalUpdate(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_vpcep_service" "test" { + name = "%s" + server_type = "VM" + vpc_id = data.huaweicloud_vpc.myvpc.id + port_id = huaweicloud_compute_instance.ecs.network[0].port + approval = true + + port_mapping { + service_port = 8080 + terminal_port = 80 + } + tags = { + owner = "tf-acc" + } +} + +resource "huaweicloud_vpcep_endpoint" "test" { + service_id = huaweicloud_vpcep_service.test.id + vpc_id = data.huaweicloud_vpc.myvpc.id + network_id = data.huaweicloud_vpc_subnet.test.id + enable_dns = true + + tags = { + owner = "tf-acc" + } + lifecycle { + ignore_changes = [enable_dns] + } +} + +resource "huaweicloud_vpcep_approval" "approval" { + service_id = huaweicloud_vpcep_service.test.id + endpoints = [] +} +`, testAccVPCEndpointPrecondition(rName), rName) +}