From e2e6dc2d59f9d5f58f6df4b9a5dfb9eda3bbc40a Mon Sep 17 00:00:00 2001 From: zhukun <2019229048@tju.edu.cn> Date: Tue, 17 Oct 2023 21:45:45 +0800 Subject: [PATCH] feat(apig):import apig resource and add unit test and document --- docs/resources/api_gateway_environment.md | 47 ++ docs/resources/apig_acl_policy.md | 91 ++++ docs/resources/apig_acl_policy_associate.md | 56 +++ docs/resources/apig_appcode.md | 71 +++ .../apig_application_authorization.md | 70 +++ docs/resources/apig_channel.md | 337 +++++++++++++++ flexibleengine/acceptance/common.go | 2 +- ...ibleengine_api_gateway_environment_test.go | 99 +++++ ...leengine_apig_acl_policy_associate_test.go | 261 ++++++++++++ ...rce_flexibleengine_apig_acl_policy_test.go | 220 ++++++++++ ...source_flexibleengine_apig_appcode_test.go | 157 +++++++ ...ine_apig_application_authorization_test.go | 209 +++++++++ ...source_flexibleengine_apig_channel_test.go | 403 ++++++++++++++++++ flexibleengine/provider.go | 7 + 14 files changed, 2029 insertions(+), 1 deletion(-) create mode 100644 docs/resources/api_gateway_environment.md create mode 100644 docs/resources/apig_acl_policy.md create mode 100644 docs/resources/apig_acl_policy_associate.md create mode 100644 docs/resources/apig_appcode.md create mode 100644 docs/resources/apig_application_authorization.md create mode 100644 docs/resources/apig_channel.md create mode 100644 flexibleengine/acceptance/resource_flexibleengine_api_gateway_environment_test.go create mode 100644 flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_associate_test.go create mode 100644 flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_test.go create mode 100644 flexibleengine/acceptance/resource_flexibleengine_apig_appcode_test.go create mode 100644 flexibleengine/acceptance/resource_flexibleengine_apig_application_authorization_test.go create mode 100644 flexibleengine/acceptance/resource_flexibleengine_apig_channel_test.go diff --git a/docs/resources/api_gateway_environment.md b/docs/resources/api_gateway_environment.md new file mode 100644 index 00000000..afedcf23 --- /dev/null +++ b/docs/resources/api_gateway_environment.md @@ -0,0 +1,47 @@ +--- +subcategory: "API Gateway (Shared APIG)" +--- + +# huaweicloud_api_gateway_environment + +Manages a shared APIG environment resource within HuaweiCloud. + +## Example Usage + +```hcl +resource "huaweicloud_api_gateway_environment" "test_env" { + name = "test" + description = "test env" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the shared APIG environment is located. + If omitted, the provider-level region will be used. Changing this will create a new resource. + +* `name` - (Required, String) Specifies the environment name. + The valid length is limited from `3` to `64`, only letters, digits and underscores (_) are allowed. + The name must start with a letter. + +* `description` - (Optional, String) Specifies the environment description. + The value can contain a maximum of `255` characters. + Chinese characters must be in **UTF-8** or **Unicode** format. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The environment ID. + +* `created_at` - The time when the shared APIG environment was created. + +## Import + +APIG environments can be imported using the `id`, e.g. + +``` +$ terraform import huaweicloud_api_gateway_environment.test_env 774438a28a574ac8a496325d1bf51807 +``` diff --git a/docs/resources/apig_acl_policy.md b/docs/resources/apig_acl_policy.md new file mode 100644 index 00000000..34c20eb5 --- /dev/null +++ b/docs/resources/apig_acl_policy.md @@ -0,0 +1,91 @@ +--- +subcategory: "API Gateway (Dedicated APIG)" +--- + +# flexibleengine_apig_acl_policy + +Manages an ACL policy resource within HuaweiCloud. + +## Example Usage + +### Create an ACL policy with IP control + +```hcl +variable "instance_id" {} +variable "policy_name" {} +variable "ip_addresses" { + type = list(stirng) +} + +resource "flexibleengine_apig_acl_policy" "ip_rule" { + instance_id = var.instance_id + name = var.policy_name + type = "PERMIT" + entity_type = "IP" + value = join(var.ip_addresses, ",") +} +``` + +### Create an ACL policy with account control (via domain names) + +```hcl +variable "instance_id" {} +variable "policy_name" {} +variable "domain_names" { + type = list(stirng) +} + +resource "flexibleengine_apig_acl_policy" "domain_rule" { + instance_id = var.instance_id + name = var.policy_name + type = "PERMIT" + entity_type = "DOMAIN" + value = join(var.domain_names, ",") +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the ACL policy is located. + If omitted, the provider-level region will be used. Changing this will create a new resource. + +* `instance_id` - (Required, String, ForceNew) Specifies the ID of the dedicated instance to which the ACL + policy belongs. + Changing this will create a new resource. + +* `name` - (Required, String) Specifies the name of the ACL policy. + The valid length is limited from `3` to `64`, only English letters, Chinese characters, digits and underscores (_) are + allowed. The name must start with an English letter or Chinese character. + +* `type` - (Required, String) Specifies the type of the ACL policy. + The valid values are as follows: + + **PERMIT**: Allow specific IPs or accounts to access API. + + **DENY**: Forbid specific IPs or accounts to access API. + +* `entity_type` - (Required, String, ForceNew) Specifies the entity type of the ACL policy. + The valid values are as follows: + + **IP**: This rule is specified to control access to the API for specific IPs. + + **DOMAIN**: This rule is specified to control access to the API for specific accounts (specified by domain name). + + Changing this will create a new resource. + +* `value` - (Required, String) Specifies one or more objects from which the access will be controlled. + Separate multiple objects with commas (,). + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the ACL policy. + +* `updated_at` - The latest update time of the ACL policy. + +## Import + +ACL Policies can be imported using their `id` and related dedicated instance ID, separated by a slash, e.g. + +```bash +$ terraform import flexibleengine_apig_acl_policy.test / +``` diff --git a/docs/resources/apig_acl_policy_associate.md b/docs/resources/apig_acl_policy_associate.md new file mode 100644 index 00000000..845c9278 --- /dev/null +++ b/docs/resources/apig_acl_policy_associate.md @@ -0,0 +1,56 @@ +--- +subcategory: "API Gateway (Dedicated APIG)" +--- + +# flexibleengine_apig_acl_policy_associate + +Use this resource to bind the APIs to the ACL policy within Flexibleengine. + +-> An ACL policy can only create one `flexibleengine_apig_acl_policy_associate` resource. + +## Example Usage + +```hcl +variable "instance_id" {} +variable "policy_id" {} +variable "api_publish_ids" { + type = list(string) +} + +resource "flexibleengine_apig_acl_policy_associate" "test" { + instance_id = var.instance_id + policy_id = var.policy_id + publish_ids = var.api_publish_ids +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the ACL policy and the APIs are located. + If omitted, the provider-level region will be used. Changing this will create a new resource. + +* `instance_id` - (Required, String, ForceNew) Specifies the ID of the dedicated instance to which the APIs and the + ACL policy belong. + Changing this will create a new resource. + +* `policy_id` - (Required, String, ForceNew) Specifies the ACL Policy ID for APIs binding. + Changing this will create a new resource. + +* `publish_ids` - (Required, List) Specifies the publish IDs corresponding to the APIs bound by the ACL policy. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Resource ID. The format is `/`. + +## Import + +Associate resources can be imported using their `policy_id` and the APIG dedicated instance ID to which the policy +belongs, separated by a slash, e.g. + +```bash +$ terraform import flexibleengine_apig_acl_policy_associate.test / +``` diff --git a/docs/resources/apig_appcode.md b/docs/resources/apig_appcode.md new file mode 100644 index 00000000..6bd32f90 --- /dev/null +++ b/docs/resources/apig_appcode.md @@ -0,0 +1,71 @@ +--- +subcategory: "API Gateway (Dedicated APIG)" +--- + +# flexibleengine_apig_appcode + +Manages an APPCODE in application resource within FlexibleEngine. + +## Example Usage + +### Auto generate APPCODE + +```hcl +variable "instance_id" {} +variable "application_id" {} + +resource "flexibleengine_apig_appcode" "test" { + instance_id = var.instance_id + application_id = var.application_id +} +``` + +### Manually configure APPCODE + +```hcl +variable "instance_id" {} +variable "application_id" {} +variable "app_code" {} + +resource "flexibleengine_apig_appcode" "test" { + instance_id = var.instance_id + application_id = var.application_id + value = var.app_code +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the application and APPCODE are located. + If omitted, the provider-level region will be used. Changing this will create a new resource. + +* `instance_id` - (Required, String, ForceNew) Specifies the ID of the dedicated instance to which the application + and APPCODE belong. + Changing this will create a new resource. + +* `application_id` - (Required, String, ForceNew) Specifies the ID of application to which the APPCODE belongs. + Changing this will create a new resource. + +* `value` - (Optional, String, ForceNew) Specifies the APPCODE value (content). + The value can contain `64` to `180` characters, starting with a letter, plus sign (+), or slash (/). Only letters and + the following special characters are allowed: `+_!@#$%/=`. + If omitted, a random value will be generated. + Changing this will create a new resource. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The APPCODE ID. + +* `created_at` - The creation time of the APPCODE. + +## Import + +APPCODEs can be imported using related `instance_id`, `application_id` and their `id`, separated by slashes, e.g. + +```bash +$ terraform import flexibleengine_apig_appcode.test // +``` diff --git a/docs/resources/apig_application_authorization.md b/docs/resources/apig_application_authorization.md new file mode 100644 index 00000000..0b851491 --- /dev/null +++ b/docs/resources/apig_application_authorization.md @@ -0,0 +1,70 @@ +--- +subcategory: "API Gateway (Dedicated APIG)" +--- + +# flexibleengine_apig_application_authorization + +Using this resoruce to authorize APIs for application, allowing it to access the published APIs within Flexibleengine. + +-> For an application, an environment can only create one `flexibleengine_apig_application_authorization` resource (all + published APIs must belong to an environment). + +## Example Usage + +```hcl +variable "instance_id" {} +variable "application_id" {} +variable "published_env_id" {} +variable "published_api_ids" { + type = list(string) +} + +resource "flexibleengine_apig_application_authorization" "test" { + instance_id = var.instance_id + application_id = var.application_id + env_id = var.published_env_id + api_ids = var.published_api_ids +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the application and APIs are located. + If omitted, the provider-level region will be used. Changing this will create a new resource. + +* `instance_id` - (Required, String, ForceNew) Specifies the ID of the dedicated instance to which the application + and APIs belong. + Changing this will create a new resource. + +* `application_id` - (Required, String, ForceNew) Specifies the ID of the application authorized to access the APIs. + Changing this will create a new resource. + +* `env_id` - (Required, String, ForceNew) Specifies the environment ID where the APIs were published. + Changing this will create a new resource. + +* `api_ids` - (Required, List) Specifies the authorized API IDs. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID, also `/`. + +## Timeouts + +This resource provides the following timeouts configuration options: + +* `create` - Default is 3 minutes. +* `update` - Default is 3 minutes. +* `delete` - Default is 3 minutes. + +## Import + +Authorize relationships of application can be imported using related `instance_id` and their `id` (also consists of +`env_id` and `application_id`), separated by the slashes, e.g. + +```bash +$ terraform import flexibleengine_apig_application_authorization.test // +``` diff --git a/docs/resources/apig_channel.md b/docs/resources/apig_channel.md new file mode 100644 index 00000000..ab622d27 --- /dev/null +++ b/docs/resources/apig_channel.md @@ -0,0 +1,337 @@ +--- +subcategory: "API Gateway (Dedicated APIG)" +--- + +# flexibleengine_apig_channel + +Manages a channel resource within FlexibleEngine. + +-> After creating a channel of type server, you can configure it for an API of an HTTP/HTTPS backend service. + +## Example Usage + +### Create a channel of type server and use the default group to manage servers + +```hcl +variable "instance_id" {} +variable "channel_name" {} +variable "backend_servers" { + type = list(object({ + group_name = string + id = string + weight = number + })) +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = var.instance_id + name = var.channel_name + port = 8080 + + dynamic "member" { + for_each = var.backend_servers + + content { + id = member.value["id"] + weight = member.value["weight"] + } + } +} +``` + +### Create a channel of type server and use the custom group to manage servers + +```hcl +variable "instance_id" {} +variable "channel_name" {} +variable "backend_server_groups" { + type = list(object({ + name = string + description = string + weight = number + })) +} +variable "backend_servers" { + type = list(object({ + group_name = string + id = string + weight = number + })) +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = var.instance_id + name = var.channel_name + port = 8080 + + # The length of group list cannot be 0 if you want to use dynamic syntax + dynamic "member_group" { + for_each = var.backend_server_groups + + content { + name = member.value["name"] + description = member.value["description"] + weight = member.value["weight"] + } + } + + dynamic "member" { + for_each = var.backend_servers + + content { + group_name = member.value["group_name"] + id = member.value["id"] + weight = member.value["weight"] + } + } +} +``` + +### Create a channel of type microservice + +```hcl +variable "instance_id" {} +variable "channel_name" {} +variable "cluster_id" {} +variable "workload_name" {} +variable "member_groups_config" { + type = list(object({ + name = string + weight = number + microservice_port = number + microservice_labels = map(string) + })) +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = var.instance_id + name = var.channel_name + port = 80 + balance_strategy = 1 + member_type = "ip" + type = 3 + + dynamic "member_group" { + for_each = var.member_groups_config + + content { + name = member_group.value["name"] + weight = member_group.value["weight"] + microservice_port = member_group.value["microservice_port"] + microservice_labels = member_group.value["microservice_labels"] + } + } + + health_check { + protocol = "TCP" + threshold_normal = 2 + threshold_abnormal = 2 + interval = 5 + timeout = 2 + port = 65530 + path = "/" + method = "GET" + http_codes = "200,201,208-209" + enable_client_ssl = false + status = 1 + } + + microservice { + cce_config { + cluster_id = var.cluster_id + namespace = "default" + workload_type = "deployment" + workload_name = var.workload_name + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the channel is located. + If omitted, the provider-level region will be used. Changing this will create a new resource. + +* `instance_id` - (Required, String, ForceNew) Specifies the ID of the dedicated instance to which the channel + belongs. + Changing this will create a new resource. + +* `name` - (Required, String) Specifies the channel name. + The valid length is limited from `3` to `64`, only chinese and english letters, digits, hyphens (-), underscores (_) + and dots (.) are allowed. + The name must start with a Chinese or English letter. + +* `port` - (Required, Int) Specifies the default port for health check in channel. + The valid value ranges from `1` to `65,535`. + +* `balance_strategy` - (Required, Int) Specifies the distribution algorithm. + The valid values are as follows: + + **1**: Weighted round robin (WRR). + + **2**: Weighted least connections (WLC). + + **3**: Source hashing. + + **4**: URI hashing. + +* `member_type` - (Optional, String) Specifies the member type of the channel. + The valid values are as follows: + + **ip**. + + **ecs**. + +* `type` - (Optional, Int) Specifies the type of the channel. + The valid values are as follows: + + **2**: Server type. + + **3**: Microservice type. + + Defaults to **2** (server type). + +* `member_group` - (Optional, List) Specifies the backend (server) groups of the channel. + The [object](#channel_member_group) structure is documented below. + +* `member` - (Optional, List) Specifies the backend servers of the channel. + This parameter is required and only available if the `type` is **2**. + The [object](#channel_members) structure is documented below. + +* `health_check` - (Optional, List) Specifies the health configuration of cloud servers associated with the load balance + channel for APIG regularly check. + The [object](#channel_health_check) structure is documented below. + +* `microservice` - (Optional, List) Specifies the configuration of the microservice. + The [object](#channel_microservice) structure is documented below. + + +The `member_group` block supports: + +* `name` - (Required, String) Specifies the name of the member group. + +* `description` - (Optional, String) Specifies the description of the member group. + +* `weight` - (Optional, String) Specifies the weight of the current member group. + +* `microservice_version` - (Optional, String) Specifies the microservice version of the backend server group. + +* `microservice_port` - (Optional, Int) Specifies the microservice port of the backend server group. + The valid value ranges from `0` to `65535`. + +* `microservice_labels` - (Optional, Map) Specifies the microservice tags of the backend server group. + + +The `member` block supports: + +* `host` - (Optional, String) Specifies the IP address each backend servers. + Required if the `member_type` is **ecs**. + This parameter and `member.id` are alternative. + +* `id` - (Optional, String) Specifies the ECS ID for each backend servers. + Required if the `member_type` is **ecs**. + This parameter and `member.host` are alternative. + +* `name` - (Optional, String) Specifies the name of the backend server. + Required if the `member.id` is set. + This parameter and `member.host` are alternative. + +* `weight` - (Optional, Int) Specifies the weight of current backend server. + The valid value ranges from `0` to `10000`, defaults to `0`. + +* `is_backup` - (Optional, Bool) Specifies whether this member is the backup member. + Defaults to **false**. + +* `group_name` - (Optional, String) Specifies the IP address each backend servers. + If omitted, means that all backend servers are both in one group. + +* `status` - (Optional, Int) Specifies the status of the backend server. + The valid values are as follows: + + **1**: Normal. + + **2**: Abnormal. + + Defaults to **1** (normal). + +* `port` - (Optional, Int) Specifies the port of the backend server. + The valid value ranges from `0` to `65535`. + If omitted, the default port of channel will be used. + + +The `health_check` block supports: + +* `protocol` - (Required, String) Specifies the microservice for performing health check on backend servers. + The valid values are **TCP**, **HTTP** and **HTTPS**, defaults to **TCP**. + +* `threshold_normal` - (Required, Int) Specifies the the healthy threshold, which refers to the number of consecutive + successful checks required for a backend server to be considered healthy. + The valid value ranges from `1` to `10`. + +* `threshold_abnormal` - (Required, Int) Specifies the unhealthy threshold, which refers to the number of consecutive + failed checks required for a backend server to be considered unhealthy. + The valid value ranges from `1` to `10`. + +* `interval` - (Required, Int) Specifies the interval between consecutive checks, in second. + The valid value ranges from `1` to `300`. + +* `timeout` - (Required, Int) Specifies the timeout for determining whether a health check fails, in second. + The value must be less than the value of the time `interval`. + The valid value ranges from `1` to `30`. + +* `path` - (Optional, String) Specifies the destination path for health checks. + Required if the `protocol` is **HTTP** or **HTTPS**. + +* `method` - (Optional, String) Specifies the request method for health check. + The valid values are **GET** and **HEAD**. + +* `port` - (Optional, Int) Specifies the destination host port for health check. + The valid value ranges from `0` to `65535`. + +* `http_codes` - (Optional, String) Specifies the response codes for determining a successful HTTP response. + The valid value ranges from `100` to `599` and the valid formats are as follows: + + The multiple values, for example, **200,201,202**. + + The range, for example, **200-299**. + + Both multiple values and ranges, for example, **201,202,210-299**. + +* `enable_client_ssl` - (Optional, Bool) Specifies whether to enable two-way authentication. + Defaults to **false**. + +* `status` - (Optional, Int) Specifies the status of health check. + The valid values are as follows: + + **1**: Normal. + + **2**: Abnormal. + + Defaults to **1** (normal). + + +The `microservice` block supports: + +* `cce_config` - (Optional, List) Specifies the CCE microservice details. + The [object](#microservice_cce_config) structure is documented below. + + +The `cce_config` block supports: + +* `cluster_id` - (Required, String) Specifies the CCE cluster ID. + +* `namespace` - (Required, String) Specifies the namespace, such as the default namespace for CCE cluster: **default**. + +* `workload_type` - (Required, String) Specifies the workload type. + + **deployment**: Stateless load. + + **statefulset**: Stateful load. + + **daemonset**: Daemons set. + +* `workload_name` - (Required, String) Specifies the workload name. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the channel. + +* `created_at` - The time when the channel was created. + +* `status` - The current status of the channel. + + **1**: Normal. + + **2**: Abnormal. + +## Import + +Channels can be imported using their `id` and the ID of the related dedicated instance, separated by a slash, e.g. + +```bash +$ terraform import flexibleengine_apig_channel.test / +``` diff --git a/flexibleengine/acceptance/common.go b/flexibleengine/acceptance/common.go index 38eb2858..e1b86703 100644 --- a/flexibleengine/acceptance/common.go +++ b/flexibleengine/acceptance/common.go @@ -55,7 +55,7 @@ data "flexibleengine_compute_flavors_v2" "test" { memory_size = 4 } -data "flexibleengine_images_image" "test" { +data "flexibleengine_images_image_v2" "test" { name = "OBS Ubuntu 18.04" } `, testBaseNetwork(name)) diff --git a/flexibleengine/acceptance/resource_flexibleengine_api_gateway_environment_test.go b/flexibleengine/acceptance/resource_flexibleengine_api_gateway_environment_test.go new file mode 100644 index 00000000..3cd9b661 --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_api_gateway_environment_test.go @@ -0,0 +1,99 @@ +package acceptance + +import ( + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk" + "github.com/chnsz/golangsdk/openstack/apigw/shared/v1/environments" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func getEnvironmentFunc(cfg *config.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.ApiGatewayV1Client(OS_REGION_NAME) + envId := state.Primary.ID + log.Printf("[DEBUG] env id is : %s", envId) + if err != nil { + return nil, fmt.Errorf("error creating APIG client %s", err) + } + + envs, err := environments.List(client, environments.ListOpts{ + EnvName: state.Primary.Attributes["name"], + }) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] List of shared APIG environments: %#v", envs) + for i, v := range envs { + if v.Id == envId { + return &envs[i], nil + } + } + + return nil, golangsdk.ErrDefault404{} +} + +func TestAccEnvironment_basic(t *testing.T) { + var env environments.Environment + rName := "flexibleengine_api_gateway_environment.test_env" + name := acceptance.RandomAccResourceName() + updateName := acceptance.RandomAccResourceName() + + rc := acceptance.InitResourceCheck(rName, &env, getEnvironmentFunc) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccEnvironment_basic(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", name), + resource.TestCheckResourceAttr(rName, "description", "created by acc test"), + resource.TestCheckResourceAttrSet(rName, "created_at"), + ), + }, + { + Config: testAccEnvironment_update(updateName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", updateName), + resource.TestCheckResourceAttr(rName, "description", "updated by acc test"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccEnvironment_basic(rName string) string { + return fmt.Sprintf(` +resource "flexibleengine_api_gateway_environment" "test_env" { + name = "%s" + description = "created by acc test" +} +`, rName) +} + +func testAccEnvironment_update(rNameUpdate string) string { + return fmt.Sprintf(` +resource "flexibleengine_api_gateway_environment" "test_env" { + name = "%s" + description = "updated by acc test" +} +`, rNameUpdate) +} diff --git a/flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_associate_test.go b/flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_associate_test.go new file mode 100644 index 00000000..1a2976dd --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_associate_test.go @@ -0,0 +1,261 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk" + "github.com/chnsz/golangsdk/openstack/apigw/dedicated/v2/acls" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func getAclPolicyAssociateFunc(conf *config.Config, state *terraform.ResourceState) (interface{}, error) { + c, err := conf.ApigV2Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + opt := acls.ListBindOpts{ + InstanceId: state.Primary.Attributes["instance_id"], + PolicyId: state.Primary.Attributes["policy_id"], + } + resp, err := acls.ListBind(c, opt) + if len(resp) < 1 { + return nil, golangsdk.ErrDefault404{} + } + return resp, err +} + +func TestAccAclPolicyAssociate_basic(t *testing.T) { + var ( + apiDetails []acls.AclBindApiInfo + + name = acceptance.RandomAccResourceName() + rName = "flexibleengine_apig_acl_policy_associate.test" + ) + + rc := acceptance.InitResourceCheck( + rName, + &apiDetails, + getAclPolicyAssociateFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccAclPolicyAssociate_basic_step1(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttrPair(rName, "instance_id", + "flexibleengine_apig_instance.test", "id"), + resource.TestCheckResourceAttrPair(rName, "policy_id", + "flexibleengine_apig_acl_policy.test", "id"), + resource.TestCheckResourceAttr(rName, "publish_ids.#", "1"), + ), + }, + { + Config: testAccAclPolicyAssociate_basic_step2(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttrPair(rName, "instance_id", + "flexibleengine_apig_instance.test", "id"), + resource.TestCheckResourceAttrPair(rName, "policy_id", + "flexibleengine_apig_acl_policy.test", "id"), + resource.TestCheckResourceAttr(rName, "publish_ids.#", "1"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAclPolicyAssociateImportStateFunc(rName), + }, + }, + }) +} + +func testAccAclPolicyAssociateImportStateFunc(rName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[rName] + if !ok { + return "", fmt.Errorf("resource (%s) not found: %s", rName, rs) + } + if rs.Primary.Attributes["instance_id"] == "" || rs.Primary.Attributes["policy_id"] == "" { + return "", fmt.Errorf("invalid format specified for import ID, want '/', but got '%s/%s'", + rs.Primary.Attributes["instance_id"], rs.Primary.Attributes["policy_id"]) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["instance_id"], rs.Primary.Attributes["policy_id"]), nil + } +} + +func testAccAclPolicyAssociate_base(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_instance" "test" { + name = "%[2]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) +} + +resource "flexibleengine_compute_instance_v2" "test" { + name = "%[2]s" + image_id = data.flexibleengine_images_image_v2.test.id + flavor_id = data.flexibleengine_compute_flavors_v2.test.flavors[0] + availability_zone = data.flexibleengine_availability_zones.test.names[0] + + network { + uuid = flexibleengine_vpc_subnet_v1.test.id + } +} + +resource "flexibleengine_apig_group" "test" { + name = "%[2]s" + instance_id = flexibleengine_apig_instance.test.id +} + +resource "flexibleengine_apig_vpc_channel" "test" { + name = "%[2]s" + instance_id = flexibleengine_apig_instance.test.id + port = 80 + algorithm = "WRR" + protocol = "HTTP" + path = "/" + http_code = "201" + + members { + id = flexibleengine_compute_instance_v2.test.id + } +} + +resource "flexibleengine_apig_api" "test" { + instance_id = flexibleengine_apig_instance.test.id + group_id = flexibleengine_apig_group.test.id + name = "%[2]s" + type = "Public" + request_protocol = "HTTP" + request_method = "GET" + request_path = "/user_info/{user_age}" + security_authentication = "APP" + matching = "Exact" + success_response = "Success response" + failure_response = "Failed response" + description = "Created by script" + + request_params { + name = "user_age" + type = "NUMBER" + location = "PATH" + required = true + maximum = 200 + minimum = 0 + } + + backend_params { + type = "REQUEST" + name = "userAge" + location = "PATH" + value = "user_age" + } + + web { + path = "/getUserAge/{userAge}" + vpc_channel_id = flexibleengine_apig_vpc_channel.test.id + request_method = "GET" + request_protocol = "HTTP" + timeout = 30000 + } + + web_policy { + name = "%[2]s_policy1" + request_protocol = "HTTP" + request_method = "GET" + effective_mode = "ANY" + path = "/getUserAge/{userAge}" + timeout = 30000 + vpc_channel_id = flexibleengine_apig_vpc_channel.test.id + + backend_params { + type = "REQUEST" + name = "userAge" + location = "PATH" + value = "user_age" + } + + conditions { + source = "param" + param_name = "user_age" + type = "Equal" + value = "28" + } + } +} + +resource "flexibleengine_apig_environment" "test" { + count = 2 + + name = "%[2]s_${count.index}" + instance_id = flexibleengine_apig_instance.test.id +} + +resource "flexibleengine_apig_api_publishment" "test" { + count = 2 + + instance_id = flexibleengine_apig_instance.test.id + api_id = flexibleengine_apig_api.test.id + env_id = flexibleengine_apig_environment.test[count.index].id +} + +resource "flexibleengine_apig_acl_policy" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" + type = "PERMIT" + entity_type = "IP" + value = "10.201.33.4,10.30.2.15" +} +`, testBaseComputeResources(name), name) +} + +func testAccAclPolicyAssociate_basic_step1(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_acl_policy_associate" "test" { + instance_id = flexibleengine_apig_instance.test.id + policy_id = flexibleengine_apig_acl_policy.test.id + + publish_ids = [ + flexibleengine_apig_api_publishment.test[0].publish_id + ] +} +`, testAccAclPolicyAssociate_base(name)) +} + +func testAccAclPolicyAssociate_basic_step2(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_acl_policy_associate" "test" { + instance_id = flexibleengine_apig_instance.test.id + policy_id = flexibleengine_apig_acl_policy.test.id + + publish_ids = [ + flexibleengine_apig_api_publishment.test[1].publish_id + ] +} +`, testAccAclPolicyAssociate_base(name)) +} diff --git a/flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_test.go b/flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_test.go new file mode 100644 index 00000000..4feb991f --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_apig_acl_policy_test.go @@ -0,0 +1,220 @@ +package acceptance + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk/openstack/apigw/dedicated/v2/acls" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func getAclPolicyFunc(cfg *config.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.ApigV2Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + return acls.Get(client, state.Primary.Attributes["instance_id"], state.Primary.ID) +} + +// All elements' length are same. +// generateRandomStringArray is a method Used to generate the domain names and the domain IDs, and the name cannot start with a digit. +func generateRandomStringArray(count, strLen int) []string { + if count < 1 || strLen < 1 { + return nil + } + result := make([]string, count) + for i := 0; i < count; i++ { + result[i] = acctest.RandStringFromCharSet(strLen, "abcdef") + } + return result +} + +func TestAccAclPolicy_basic(t *testing.T) { + var ( + policy acls.Policy + + rName1 = "flexibleengine_apig_acl_policy.ip_rule" + rName2 = "flexibleengine_apig_acl_policy.domain_rule" + rName3 = "flexibleengine_apig_acl_policy.domain_id_rule" + name = acceptance.RandomAccResourceName() // The length is 13. + + basicDomainNames = strings.Join(generateRandomStringArray(2, 4), ",") + updateDomainNames = strings.Join(generateRandomStringArray(2, 4), ",") + basicDomainIds = strings.Join(generateRandomStringArray(2, 32), ",") + updateDomainIds = strings.Join(generateRandomStringArray(2, 32), ",") + + rc1 = acceptance.InitResourceCheck(rName1, &policy, getAclPolicyFunc) + rc2 = acceptance.InitResourceCheck(rName2, &policy, getAclPolicyFunc) + rc3 = acceptance.InitResourceCheck(rName3, &policy, getAclPolicyFunc) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc1.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccApigAclPolicy_basic_step1(name, basicDomainNames, basicDomainIds), + Check: resource.ComposeTestCheckFunc( + rc1.CheckResourceExists(), + resource.TestCheckResourceAttr(rName1, "name", name+"_rule_ip"), + resource.TestCheckResourceAttr(rName1, "type", "PERMIT"), + resource.TestCheckResourceAttr(rName1, "entity_type", "IP"), + resource.TestCheckResourceAttr(rName1, "value", "10.201.33.4,10.30.2.15"), + rc2.CheckResourceExists(), + resource.TestCheckResourceAttr(rName2, "name", name+"_rule_domain"), + resource.TestCheckResourceAttr(rName2, "type", "PERMIT"), + resource.TestCheckResourceAttr(rName2, "entity_type", "DOMAIN"), + resource.TestCheckResourceAttr(rName2, "value", basicDomainNames), + rc3.CheckResourceExists(), + resource.TestCheckResourceAttr(rName3, "name", name+"_rule_domain_id"), + resource.TestCheckResourceAttr(rName3, "type", "PERMIT"), + resource.TestCheckResourceAttr(rName3, "entity_type", "DOMAIN"), + resource.TestCheckResourceAttr(rName3, "value", basicDomainIds), + ), + }, + { + Config: testAccApigAclPolicy_basic_step2(name, updateDomainNames, updateDomainIds), + Check: resource.ComposeTestCheckFunc( + rc1.CheckResourceExists(), + resource.TestCheckResourceAttr(rName1, "name", name+"_rule_ip_update"), + resource.TestCheckResourceAttr(rName1, "type", "DENY"), + resource.TestCheckResourceAttr(rName1, "entity_type", "IP"), + resource.TestCheckResourceAttr(rName1, "value", "10.201.33.8,10.30.2.23"), + rc2.CheckResourceExists(), + resource.TestCheckResourceAttr(rName2, "name", name+"_rule_domain_update"), + resource.TestCheckResourceAttr(rName2, "type", "DENY"), + resource.TestCheckResourceAttr(rName2, "entity_type", "DOMAIN"), + resource.TestCheckResourceAttr(rName2, "value", updateDomainNames), + rc3.CheckResourceExists(), + resource.TestCheckResourceAttr(rName3, "name", name+"_rule_domain_id_update"), + resource.TestCheckResourceAttr(rName3, "type", "DENY"), + resource.TestCheckResourceAttr(rName3, "entity_type", "DOMAIN"), + resource.TestCheckResourceAttr(rName3, "value", updateDomainIds), + ), + }, + { + ResourceName: rName1, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAclPolicyImportStateFunc(rName1), + }, + { + ResourceName: rName2, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAclPolicyImportStateFunc(rName2), + }, + { + ResourceName: rName3, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAclPolicyImportStateFunc(rName3), + }, + }, + }) +} + +func testAccAclPolicyImportStateFunc(rName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[rName] + if !ok { + return "", fmt.Errorf("resource (%s) not found: %s", rName, rs) + } + if rs.Primary.Attributes["instance_id"] == "" { + return "", fmt.Errorf("invalid format specified for import ID, want '/', but '%s/%s'", + rs.Primary.Attributes["instance_id"], rs.Primary.ID) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["instance_id"], rs.Primary.ID), nil + } +} + +func testAccApigAclPolicy_base(name string) string { + return fmt.Sprintf(` +%[1]s + +data "flexibleengine_availability_zones" "test" {} + +resource "flexibleengine_apig_instance" "test" { + name = "%[2]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 = [ + data.flexibleengine_availability_zones.test.names[0], + ] +} +`, testBaseNetwork(name), name) +} + +func testAccApigAclPolicy_basic_step1(name, domainNames, domainIds string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_acl_policy" "ip_rule" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s_rule_ip" + type = "PERMIT" + entity_type = "IP" + value = "10.201.33.4,10.30.2.15" +} + +resource "flexibleengine_apig_acl_policy" "domain_rule" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s_rule_domain" + type = "PERMIT" + entity_type = "DOMAIN" + value = "%[3]s" +} + +resource "flexibleengine_apig_acl_policy" "domain_id_rule" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s_rule_domain_id" + type = "PERMIT" + entity_type = "DOMAIN" + value = "%[4]s" +} +`, testAccApigAclPolicy_base(name), name, domainNames, domainIds) +} + +func testAccApigAclPolicy_basic_step2(name, domainNames, domainIds string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_acl_policy" "ip_rule" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s_rule_ip_update" + type = "DENY" + entity_type = "IP" + value = "10.201.33.8,10.30.2.23" +} + +resource "flexibleengine_apig_acl_policy" "domain_rule" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s_rule_domain_update" + type = "DENY" + entity_type = "DOMAIN" + value = "%[3]s" +} + +resource "flexibleengine_apig_acl_policy" "domain_id_rule" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s_rule_domain_id_update" + type = "DENY" + entity_type = "DOMAIN" + value = "%[4]s" +} +`, testAccApigAclPolicy_base(name), name, domainNames, domainIds) +} diff --git a/flexibleengine/acceptance/resource_flexibleengine_apig_appcode_test.go b/flexibleengine/acceptance/resource_flexibleengine_apig_appcode_test.go new file mode 100644 index 00000000..08bd657c --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_apig_appcode_test.go @@ -0,0 +1,157 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk/openstack/apigw/dedicated/v2/applications" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" +) + +func getAppcodeFunc(cfg *config.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.ApigV2Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + return applications.GetAppCode(client, state.Primary.Attributes["instance_id"], + state.Primary.Attributes["application_id"], state.Primary.ID).Extract() +} + +// Auto generate APPCODE. +func TestAccAppcode_basic(t *testing.T) { + var ( + appCode applications.AppCode + + rName = "flexibleengine_apig_appcode.test" + rc = acceptance.InitResourceCheck(rName, &appCode, getAppcodeFunc) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccAppcode_basic(), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAppcodeImportIdFunc(), + }, + }, + }) +} + +func testAccAppcodeImportIdFunc() resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rName := "flexibleengine_apig_appcode.test" + rs, ok := s.RootModule().Resources[rName] + if !ok { + return "", fmt.Errorf("Resource (%s) not found: %s", rName, rs) + } + instanceId := rs.Primary.Attributes["instance_id"] + appId := rs.Primary.Attributes["application_id"] + appCodeId := rs.Primary.ID + if instanceId == "" || appId == "" || appCodeId == "" { + return "", fmt.Errorf("invalid format specified for import ID, want '//', but got '%s/%s/%s'", + instanceId, appId, appCodeId) + } + return fmt.Sprintf("%s/%s/%s", instanceId, appId, appCodeId), nil + } +} + +func testAccApigAppcode_base() string { + name := acceptance.RandomAccResourceName() + + return fmt.Sprintf(` +%[1]s + +data "flexibleengine_availability_zones" "test" {} + +resource "flexibleengine_apig_instance" "test" { + name = "%[2]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) +} + +resource "flexibleengine_apig_application" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" +} +`, testBaseNetwork(name), name) +} + +func testAccAppcode_basic() string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_appcode" "test" { + instance_id = flexibleengine_apig_instance.test.id + application_id = flexibleengine_apig_application.test.id +} +`, testAccApigAppcode_base()) +} + +// Manually configure APPCODE. +func TestAccAppcode_manuallyConfig(t *testing.T) { + var ( + appCode applications.AppCode + + rName = "flexibleengine_apig_appcode.test" + rc = acceptance.InitResourceCheck(rName, &appCode, getAppcodeFunc) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccAppcode_manuallyConfig(), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAppcodeImportIdFunc(), + }, + }, + }) +} + +func testAccAppcode_manuallyConfig() string { + code := utils.Base64EncodeString(acctest.RandString(64)) + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_appcode" "test" { + instance_id = flexibleengine_apig_instance.test.id + application_id = flexibleengine_apig_application.test.id + value = "%[2]s" +} +`, testAccApigAppcode_base(), code) +} diff --git a/flexibleengine/acceptance/resource_flexibleengine_apig_application_authorization_test.go b/flexibleengine/acceptance/resource_flexibleengine_apig_application_authorization_test.go new file mode 100644 index 00000000..3fdf66aa --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_apig_application_authorization_test.go @@ -0,0 +1,209 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk" + "github.com/chnsz/golangsdk/openstack/apigw/dedicated/v2/appauths" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func getAppAuthFunc(cfg *config.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.ApigV2Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + + opts := appauths.ListOpts{ + InstanceId: state.Primary.Attributes["instance_id"], + AppId: state.Primary.Attributes["application_id"], + } + resp, err := appauths.ListAuthorized(client, opts) + if err != nil { + return nil, err + } + if len(resp) < 1 { + return nil, golangsdk.ErrDefault404{} + } + return resp, nil +} + +func TestAccAppAuth_basic(t *testing.T) { + var ( + authApis []appauths.ApiAuthInfo + + rName = "flexibleengine_apig_application_authorization.test" + rc = acceptance.InitResourceCheck(rName, &authApis, getAppAuthFunc) + baseConfig = testAccAppAuth_base() + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccAppAuth_basic_step1(baseConfig), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + ), + }, + { + Config: testAccAppAuth_basic_step2(baseConfig), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAppAuthImportIdFunc(rName), + }, + }, + }) +} + +func testAccAppAuthImportIdFunc(rsName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[rsName] + if !ok { + return "", fmt.Errorf("resource (%s) not found: %s", rsName, rs) + } + + instanceId := rs.Primary.Attributes["instance_id"] + resourceId := rs.Primary.ID + if instanceId == "" || resourceId == "" { + return "", fmt.Errorf("missing some attributes, want '/' (the format of resource ID is "+ + "'/'), but got '%s/%s'", instanceId, resourceId) + } + return fmt.Sprintf("%s/%s", instanceId, resourceId), nil + } +} + +func testAccAppAuth_base() string { + name := acceptance.RandomAccResourceName() + + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_compute_instance_v2" "test" { + name = "%[2]s" + image_id = data.flexibleengine_images_image_v2.test.id + flavor_id = data.flexibleengine_compute_flavors_v2.test.flavors[0] + availability_zone = data.flexibleengine_availability_zones.test.names[0] + + network { + uuid = flexibleengine_vpc_subnet_v1.test.id + } +} + +resource "flexibleengine_apig_instance" "test" { + name = "%[2]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) +} + +resource "flexibleengine_apig_group" "test" { + name = "%[2]s" + instance_id = flexibleengine_apig_instance.test.id +} + +resource "flexibleengine_apig_vpc_channel" "test" { + name = "%[2]s" + instance_id = flexibleengine_apig_instance.test.id + port = 80 + algorithm = "WRR" + protocol = "HTTP" + path = "/" + http_code = "201" + + members { + id = flexibleengine_compute_instance_v2.test.id + } +} + +resource "flexibleengine_apig_api" "test" { + count = 3 + + instance_id = flexibleengine_apig_instance.test.id + group_id = flexibleengine_apig_group.test.id + name = "%[2]s_${count.index}" + type = "Public" + request_protocol = "HTTP" + request_method = "GET" + request_path = "/user_info/${count.index}" + security_authentication = "APP" + matching = "Exact" + + web { + path = "/getUserAge/${count.index}" + vpc_channel_id = flexibleengine_apig_vpc_channel.test.id + request_method = "GET" + request_protocol = "HTTP" + timeout = 30000 + } +} + +resource "flexibleengine_apig_environment" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" +} + +resource "flexibleengine_apig_api_publishment" "test" { + count = 3 + + instance_id = flexibleengine_apig_instance.test.id + api_id = flexibleengine_apig_api.test[count.index].id + env_id = flexibleengine_apig_environment.test.id +} + +resource "flexibleengine_apig_application" "test" { + instance_id = flexibleengine_apig_instance.test.id// flexibleengine_apig_instance.test.id + name = "%[2]s" +} +`, testBaseComputeResources(name), name) +} + +func testAccAppAuth_basic_step1(baseConfig string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_application_authorization" "test" { + depends_on = [flexibleengine_apig_api_publishment.test] + + instance_id = flexibleengine_apig_instance.test.id + application_id = flexibleengine_apig_application.test.id + env_id = flexibleengine_apig_environment.test.id + api_ids = slice(flexibleengine_apig_api.test[*].id, 0, 2) +} +`, baseConfig) +} + +func testAccAppAuth_basic_step2(baseConfig string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_application_authorization" "test" { + depends_on = [flexibleengine_apig_api_publishment.test] + + instance_id = flexibleengine_apig_instance.test.id + application_id = flexibleengine_apig_application.test.id + env_id = flexibleengine_apig_environment.test.id + api_ids = slice(flexibleengine_apig_api.test[*].id, 1, 3) +} +`, baseConfig) +} diff --git a/flexibleengine/acceptance/resource_flexibleengine_apig_channel_test.go b/flexibleengine/acceptance/resource_flexibleengine_apig_channel_test.go new file mode 100644 index 00000000..1249afb8 --- /dev/null +++ b/flexibleengine/acceptance/resource_flexibleengine_apig_channel_test.go @@ -0,0 +1,403 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk/openstack/apigw/dedicated/v2/channels" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func getChannelFunc(cfg *config.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.ApigV2Client(OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + return channels.Get(client, state.Primary.Attributes["instance_id"], state.Primary.ID) +} + +func TestAccChannel_basic(t *testing.T) { + var ( + channel channels.Channel + + // Only letters, digits and underscores (_) are allowed in the environment name and dedicated instance name. + rName = "flexibleengine_apig_channel.test" + name = acceptance.RandomAccResourceName() + updateName = acceptance.RandomAccResourceName() + ) + + rc := acceptance.InitResourceCheck( + rName, + &channel, + getChannelFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccChannel_basic_step1(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", name), + resource.TestCheckResourceAttr(rName, "port", "80"), + resource.TestCheckResourceAttr(rName, "balance_strategy", "1"), + resource.TestCheckResourceAttr(rName, "member_type", "ecs"), + resource.TestCheckResourceAttr(rName, "type", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.protocol", "TCP"), + resource.TestCheckResourceAttr(rName, "health_check.0.threshold_normal", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.threshold_abnormal", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.interval", "5"), + resource.TestCheckResourceAttr(rName, "health_check.0.timeout", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.path", ""), + resource.TestCheckResourceAttr(rName, "health_check.0.method", ""), + resource.TestCheckResourceAttr(rName, "health_check.0.port", "0"), + resource.TestCheckResourceAttr(rName, "health_check.0.http_codes", ""), + resource.TestCheckResourceAttr(rName, "member.#", "1"), + ), + }, + { + Config: testAccChannel_basic_step2(updateName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", updateName), + resource.TestCheckResourceAttr(rName, "port", "8000"), + resource.TestCheckResourceAttr(rName, "balance_strategy", "2"), + resource.TestCheckResourceAttr(rName, "member_type", "ecs"), + resource.TestCheckResourceAttr(rName, "type", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.protocol", "HTTPS"), + resource.TestCheckResourceAttr(rName, "health_check.0.threshold_normal", "10"), + resource.TestCheckResourceAttr(rName, "health_check.0.threshold_abnormal", "10"), + resource.TestCheckResourceAttr(rName, "health_check.0.interval", "300"), + resource.TestCheckResourceAttr(rName, "health_check.0.timeout", "30"), + resource.TestCheckResourceAttr(rName, "health_check.0.path", "/terraform/"), + resource.TestCheckResourceAttr(rName, "health_check.0.method", "HEAD"), + resource.TestCheckResourceAttr(rName, "health_check.0.port", "8080"), + resource.TestCheckResourceAttr(rName, "health_check.0.http_codes", "201,202,303-404"), + resource.TestCheckResourceAttr(rName, "member.#", "2"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccChannelImportStateFunc(), + }, + }, + }) +} + +func testAccChannelImportStateFunc() resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rName := "flexibleengine_apig_channel.test" + rs, ok := s.RootModule().Resources[rName] + if !ok { + return "", fmt.Errorf("Resource (%s) not found: %s", rName, rs) + } + if rs.Primary.Attributes["instance_id"] == "" || rs.Primary.ID == "" { + return "", fmt.Errorf("resource not found: %s/%s", rs.Primary.Attributes["instance_id"], + rs.Primary.ID) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["instance_id"], rs.Primary.ID), nil + } +} + +func testAccChannel_base(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_apig_instance" "test" { + name = "%[2]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) +} + +`, testBaseComputeResources(name), name) +} + +func testAccChannel_basic_step1(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_compute_instance_v2" "test" { + count = 1 + + name = format("%[2]s-%%d", count.index) + image_id = data.flexibleengine_images_image_v2.test.id + flavor_id = data.flexibleengine_compute_flavors_v2.test.flavors[0] + availability_zone = data.flexibleengine_availability_zones.test.names[0] + + network { + uuid = flexibleengine_vpc_subnet_v1.test.id + } +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" + port = 80 + balance_strategy = 1 + member_type = "ecs" + type = 2 + + health_check { + protocol = "TCP" + threshold_normal = 2 # minimum value + threshold_abnormal = 2 # minimum value + interval = 5 # minimum value + timeout = 2 # minimum value + } + + dynamic "member" { + for_each = flexibleengine_compute_instance_v2.test[*] + + content { + id = member.value.id + name = member.value.name + } + } +} +`, testAccChannel_base(name), name) +} + +func testAccChannel_basic_step2(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_compute_instance_v2" "test" { + count = 2 + + name = format("%[2]s-%%d", count.index) + image_id = data.flexibleengine_images_image_v2.test.id + flavor_id = data.flexibleengine_compute_flavors_v2.test.flavors[0] + availability_zone = data.flexibleengine_availability_zones.test.names[0] + + network { + uuid = flexibleengine_vpc_subnet_v1.test.id + } +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" + port = 8000 + balance_strategy = 2 + member_type = "ecs" + type = 2 + + health_check { + protocol = "HTTPS" + threshold_normal = 10 # maximum value + threshold_abnormal = 10 # maximum value + interval = 300 # maximum value + timeout = 30 # maximum value + path = "/terraform/" + method = "HEAD" + port = 8080 + http_codes = "201,202,303-404" + } + + dynamic "member" { + for_each = flexibleengine_compute_instance_v2.test[*] + + content { + id = member.value.id + name = member.value.name + } + } +} +`, testAccChannel_base(name), name) +} + +func TestAccChannel_eipMembers(t *testing.T) { + var ( + channel channels.Channel + + // Only letters, digits and underscores (_) are allowed in the environment name and dedicated instance name. + rName = "flexibleengine_apig_channel.test" + name = acceptance.RandomAccResourceName() + ) + + rc := acceptance.InitResourceCheck( + rName, + &channel, + getChannelFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccChannel_eipMembers_step1(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", name), + resource.TestCheckResourceAttr(rName, "port", "80"), + resource.TestCheckResourceAttr(rName, "balance_strategy", "2"), + resource.TestCheckResourceAttr(rName, "member_type", "ip"), + resource.TestCheckResourceAttr(rName, "type", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.protocol", "HTTP"), + resource.TestCheckResourceAttr(rName, "health_check.0.threshold_normal", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.threshold_abnormal", "2"), + resource.TestCheckResourceAttr(rName, "health_check.0.interval", "60"), + resource.TestCheckResourceAttr(rName, "health_check.0.timeout", "10"), + resource.TestCheckResourceAttr(rName, "health_check.0.path", "/"), + resource.TestCheckResourceAttr(rName, "health_check.0.method", "HEAD"), + resource.TestCheckResourceAttr(rName, "health_check.0.port", "8080"), + resource.TestCheckResourceAttr(rName, "health_check.0.http_codes", "201,202,303-404"), + resource.TestCheckResourceAttr(rName, "member.#", "1"), + ), + }, + { + Config: testAccChannel_eipMembers_step2(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", name), + resource.TestCheckResourceAttr(rName, "member.#", "2"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccChannelImportStateFunc(), + }, + }, + }) +} + +func testAccChannel_eipBase(name string) string { + return fmt.Sprintf(` +%[1]s + +data "flexibleengine_availability_zones" "test" {} + +resource "flexibleengine_apig_instance" "test" { + name = "%[2]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) +} +`, testBaseNetwork(name), name) +} + +func testAccChannel_eipMembers_step1(rName string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_vpc_eip" "test" { + count = 1 + + publicip { + type = "5_bgp" + } + + bandwidth { + name = format("%[2]s-%%d", count.index) + size = 5 + share_type = "PER" + charge_mode = "traffic" + } +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" + port = 80 + balance_strategy = 2 + member_type = "ip" + type = 2 + + health_check { + protocol = "HTTP" + threshold_normal = 2 + threshold_abnormal = 2 + interval = 60 + timeout = 10 + path = "/" + method = "HEAD" + port = 8080 + http_codes = "201,202,303-404" + } + + dynamic "member" { + for_each = flexibleengine_vpc_eip.test[*].address + + content { + host = member.value + } + } +} +`, testAccChannel_eipBase(rName), rName) +} + +func testAccChannel_eipMembers_step2(rName string) string { + return fmt.Sprintf(` +%[1]s + +resource "flexibleengine_vpc_eip" "test" { + count = 2 + + publicip { + type = "5_bgp" + } + + bandwidth { + name = format("%[2]s-%%d", count.index) + size = 5 + share_type = "PER" + charge_mode = "traffic" + } +} + +resource "flexibleengine_apig_channel" "test" { + instance_id = flexibleengine_apig_instance.test.id + name = "%[2]s" + port = 80 + balance_strategy = 2 + member_type = "ip" + type = 2 + + health_check { + protocol = "HTTP" + threshold_normal = 2 + threshold_abnormal = 2 + interval = 60 + timeout = 10 + path = "/" + method = "HEAD" + port = 8080 + http_codes = "201,202,303-404" + } + + dynamic "member" { + for_each = flexibleengine_vpc_eip.test[*].address + + content { + host = member.value + } + } +} +`, testAccChannel_eipBase(rName), rName) +} diff --git a/flexibleengine/provider.go b/flexibleengine/provider.go index 7b666ee5..e5880f45 100644 --- a/flexibleengine/provider.go +++ b/flexibleengine/provider.go @@ -14,6 +14,7 @@ import ( "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/helper/mutexkv" "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/apig" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/apigateway" "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/as" "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/cbr" "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/cce" @@ -449,10 +450,15 @@ func Provider() *schema.Provider { "flexibleengine_dli_queue": ResourceDliQueueV1(), // importing new resource + "flexibleengine_apig_acl_policy": apig.ResourceAclPolicy(), + "flexibleengine_apig_acl_policy_associate": apig.ResourceAclPolicyAssociate(), "flexibleengine_apig_api": apig.ResourceApigAPIV2(), "flexibleengine_apig_api_publishment": apig.ResourceApigApiPublishment(), "flexibleengine_apig_instance": apig.ResourceApigInstanceV2(), + "flexibleengine_apig_appcode": apig.ResourceAppcode(), "flexibleengine_apig_application": apig.ResourceApigApplicationV2(), + "flexibleengine_apig_application_authorization": apig.ResourceAppAuth(), + "flexibleengine_apig_channel": apig.ResourceChannel(), "flexibleengine_apig_custom_authorizer": apig.ResourceApigCustomAuthorizerV2(), "flexibleengine_apig_environment": apig.ResourceApigEnvironmentV2(), "flexibleengine_apig_group": apig.ResourceApigGroupV2(), @@ -461,6 +467,7 @@ func Provider() *schema.Provider { "flexibleengine_apig_throttling_policy": apig.ResourceApigThrottlingPolicyV2(), "flexibleengine_api_gateway_api": huaweicloud.ResourceAPIGatewayAPI(), + "flexibleengine_api_gateway_environment": apigateway.ResourceEnvironment(), "flexibleengine_api_gateway_group": huaweicloud.ResourceAPIGatewayGroup(), "flexibleengine_as_instance_attach": as.ResourceASInstanceAttach(),