diff --git a/docs/data-sources/apigw_gateway_features_v2.md b/docs/data-sources/apigw_gateway_features_v2.md new file mode 100644 index 000000000..44c0e3581 --- /dev/null +++ b/docs/data-sources/apigw_gateway_features_v2.md @@ -0,0 +1,57 @@ +--- +subcategory: "APIGW" +layout: "opentelekomcloud" +page_title: "OpenTelekomCloud: opentelekomcloud_apigw_gateway_features_v2" +sidebar_current: "docs-opentelekomcloud-datasource-apigw-gateway-features-v2" +description: |- + Get the all APIGW gateway features from OpenTelekomCloud +--- + +Up-to-date reference of API arguments for API Gateway environment variable service you can get at +[documentation portal](https://docs.otc.t-systems.com/api-gateway/api-ref/dedicated_gateway_apis_v2/gateway_feature_management/querying_gateway_features.html) + +# opentelekomcloud_apigw_gateway_features_v2 + +Use this data source to get the list of the features under the APIGW gateway within OpenTelekomCloud. + +## Example Usage + +```hcl +variable gateway_id {} + +data "opentelekomcloud_apigw_gateway_features_v2" "ft" { + gateway_id = var.gateway_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `gateway_id` - (Required, String) Specified the ID of the dedicated gateway to which the features belong. + +* `name` - (Optional, String) Specified the name of the feature. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The data source ID. + +* `region` - The region in which to query the data source. + +* `features` - All instance features that match the filter parameters. + The [features](#instance_features) structure is documented below. + + +The `features` block supports: + +* `id` - The ID of the feature. + +* `name` - The name of the feature. + +* `enabled` - Whether the feature is enabled. + +* `config` - The detailed configuration of the instance feature. + +* `updated_at` - The latest update time of the feature, in RFC3339 format. diff --git a/docs/resources/apigw_gateway_feature_v2.md b/docs/resources/apigw_gateway_feature_v2.md new file mode 100644 index 000000000..f14c35db0 --- /dev/null +++ b/docs/resources/apigw_gateway_feature_v2.md @@ -0,0 +1,70 @@ +--- +subcategory: "APIGW" +layout: "opentelekomcloud" +page_title: "OpenTelekomCloud: opentelekomcloud_apigw_gateway_feature_v2" +sidebar_current: "docs-opentelekomcloud-resource-apigw-gateway-feature-v2" +description: |- + Manages a APIGW gateway feature resource within OpenTelekomCloud. +--- + +Up-to-date reference of API arguments for API Gateway environment variable service you can get at +[documentation portal](https://docs.otc.t-systems.com/api-gateway/api-ref/dedicated_gateway_apis_v2/gateway_feature_management/configuring_a_feature_for_a_gateway.html) + +# opentelekomcloud_apigw_gateway_feature_v2 + +Manages an APIGW gateway feature resource within OpenTelekomCloud. + +-> For various types of feature parameter configurations, please refer to the + [documentation](https://docs.otc.t-systems.com/api-gateway/api-ref/appendix/supported_features.html#apig-api-20200402). + +## Example Usage + +```hcl +variable "gateway_id" {} + +resource "opentelekomcloud_apigw_gateway_feature_v2" "feat" { + gateway_id = var.gateway_id + name = "ratelimit" + enabled = true + + config = jsonencode({ + api_limits = 300 + }) +} +``` + +## Argument Reference + +The following arguments are supported: + +* `gateway_id` - (Required, String, ForceNew) Specified the ID of the dedicated gateway to which the feature belongs. + Changing this creates a new resource. + +* `name` - (Required, String, ForceNew) Specified the name of the feature. + Changing this creates a new resource. + +* `enabled` - (Optional, Bool) Specified whether to enable the feature. Default value is `false`. + +* `config` - (Optional, String) Specified the detailed configuration of the feature. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID. The value is the feature name. + +* `region` - The region in which to create the resource. + +## Timeouts + +This resource provides the following timeouts configuration options: + +* `create` - Default is 5 minutes. + +## Import + +The resource can be imported using `gateway_id` and `name`, separated by a slash (/), e.g. + +```bash +$ terraform import opentelekomcloud_apigw_gateway_feature_v2.feat / +``` diff --git a/docs/resources/apigw_gateway_routes_v2.md b/docs/resources/apigw_gateway_routes_v2.md new file mode 100644 index 000000000..1e6a2772c --- /dev/null +++ b/docs/resources/apigw_gateway_routes_v2.md @@ -0,0 +1,53 @@ +--- +subcategory: "APIGW" +layout: "opentelekomcloud" +page_title: "OpenTelekomCloud: opentelekomcloud_apigw_gateway_routes_v2" +sidebar_current: "docs-opentelekomcloud-resource-apigw-gateway-routes-v2" +description: |- + Manages a APIGW gateway routes resource within OpenTelekomCloud. +--- + +Up-to-date reference of API arguments for API Gateway environment variable service you can get at +[documentation portal](https://docs.otc.t-systems.com/api-gateway/api-ref/dedicated_gateway_apis_v2/gateway_feature_management/configuring_a_feature_for_a_gateway.html) + +# opentelekomcloud_apigw_gateway_routes_v2 + +Manages a APIGW gateway routes resource within OpenTelekomCloud. + +## Example Usage + +```hcl +variable "gateway_id" {} + +resource "opentelekomcloud_apigw_gateway_routes_v2" "rt" { + gateway_id = var.gateway_id + nexthops = ["172.16.3.0/24", "172.16.7.0/24"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `gateway_id` - (Required, String, ForceNew) Specifies the ID of the dedicated gateway to which the routes belong. + Changing this will create a new resource. + +* `nexthops` - (Required, List) Specifies the configuration of the next-hop routes. + +-> The network segment of the next hop cannot overlap with the network segment of the APIGW gateway. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID (gateway ID). + +* `region` - The region where the dedicated gateway and routes are located. + +## Import + +Routes can be imported using their related dedicated instance ID (`gateway_id`), e.g. + +```bash +$ terraform import opentelekomcloud_apigw_gateway_routes_v2.rt 628001b3c5eg6d3e91a8da530f46427y +``` diff --git a/opentelekomcloud/acceptance/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2_test.go b/opentelekomcloud/acceptance/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2_test.go new file mode 100644 index 000000000..0ba3a531a --- /dev/null +++ b/opentelekomcloud/acceptance/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2_test.go @@ -0,0 +1,99 @@ +package acceptance + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/env" +) + +func TestAccDataSourceInstanceFeatures_basic(t *testing.T) { + var ( + rName = "data.opentelekomcloud_apigw_gateway_features_v2.test" + dc = common.InitDataSourceCheck(rName) + + byName = "data.opentelekomcloud_apigw_gateway_features_v2.filter_by_name" + dcByName = common.InitDataSourceCheck(byName) + + byNotFoundName = "data.opentelekomcloud_apigw_gateway_features_v2.filter_by_not_found_name" + dcByNotFoundName = common.InitDataSourceCheck(byNotFoundName) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + common.TestAccPreCheck(t) + common.TestAccPreCheckApigw(t) + }, + ProviderFactories: common.TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceInstanceFeatures_basic(), + Check: resource.ComposeTestCheckFunc( + dc.CheckResourceExists(), + resource.TestMatchResourceAttr(rName, "features.#", regexp.MustCompile(`^[1-9]([0-9]*)?$`)), + dcByName.CheckResourceExists(), + resource.TestMatchResourceAttr(byName, "features.0.updated_at", + regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}?(Z|([+-]\d{2}:\d{2}))$`)), + resource.TestCheckOutput("is_name_filter_useful", "true"), + dcByNotFoundName.CheckResourceExists(), + resource.TestCheckOutput("is_name_not_found_filter_useful", "true"), + ), + }, + }, + }) +} + +func testAccDataSourceInstanceFeatures_basic() string { + return fmt.Sprintf(` +locals { + gateway_id = "%[1]s" +} + +data "opentelekomcloud_apigw_gateway_features_v2" "test" { + gateway_id = local.gateway_id +} + +# Filter by name +locals { + feature_name = data.opentelekomcloud_apigw_gateway_features_v2.test.features[0].name +} + +data "opentelekomcloud_apigw_gateway_features_v2" "filter_by_name" { + gateway_id = local.gateway_id + name = local.feature_name +} + +locals { + name_filter_result = [ + for v in data.opentelekomcloud_apigw_gateway_features_v2.filter_by_name.features[*].name : v == local.feature_name + ] +} + +output "is_name_filter_useful" { + value = length(local.name_filter_result) > 0 && alltrue(local.name_filter_result) +} + +# Filter by name (not found) +locals { + not_found_name = "not_found" +} + +data "opentelekomcloud_apigw_gateway_features_v2" "filter_by_not_found_name" { + gateway_id = local.gateway_id + name = local.not_found_name +} + +locals { + not_found_name_filter_result = [ + for v in data.opentelekomcloud_apigw_gateway_features_v2.filter_by_not_found_name.features[*].name : strcontains(v, local.not_found_name) + ] +} + +output "is_name_not_found_filter_useful" { + value = length(local.not_found_name_filter_result) == 0 +} +`, env.OS_APIGW_GATEWAY_ID) +} diff --git a/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2_test.go b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2_test.go new file mode 100644 index 000000000..32d39cc6f --- /dev/null +++ b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2_test.go @@ -0,0 +1,130 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/env" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" +) + +func getInstanceFeatureFunc(cfg *cfg.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.APIGWV2Client(env.OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + gatewayId := state.Primary.Attributes["gateway_id"] + features, err := gateway.ListGatewayFeatures(client, gateway.ListFeaturesOpts{ + GatewayID: gatewayId, + Limit: 500, + }) + if err != nil { + return nil, err + } + if len(features) < 1 { + return nil, err + } + var f gateway.FeatureResp + for _, feature := range features { + if feature.Name == state.Primary.ID { + f = feature + } + } + return f, err +} + +func TestAccInstanceFeature_basic(t *testing.T) { + var ( + feature gateway.FeatureResp + rName = "opentelekomcloud_apigw_gateway_feature_v2.feat" + ) + + rc := common.InitResourceCheck( + rName, + &feature, + getInstanceFeatureFunc, + ) + + // Avoid CheckDestroy because this resource already exists and does not need to be deleted. + // lintignore:AT001 + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + common.TestAccPreCheck(t) + common.TestAccPreCheckApigw(t) + }, + ProviderFactories: common.TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccInstanceFeature_basic(), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", "ratelimit"), + resource.TestCheckResourceAttr(rName, "enabled", "true"), + resource.TestCheckResourceAttr(rName, "config", "{\"api_limits\":200}"), + ), + }, + { + Config: testAccInstanceFeature_basicUpdate(), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "name", "ratelimit"), + resource.TestCheckResourceAttr(rName, "config", "{\"api_limits\":300}"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccInstanceFeatureResourceImportStateFunc(rName), + }, + }, + }) +} + +func testAccInstanceFeatureResourceImportStateFunc(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) + } + gatewayId := rs.Primary.Attributes["gateway_id"] + featureName := rs.Primary.ID + if gatewayId == "" || featureName == "" { + return "", fmt.Errorf("missing some attributes, want '/', but '%s/%s'", + gatewayId, featureName) + } + return fmt.Sprintf("%s/%s", gatewayId, featureName), nil + } +} + +func testAccInstanceFeature_basic() string { + return fmt.Sprintf(` +resource "opentelekomcloud_apigw_gateway_feature_v2" "feat" { + gateway_id = "%[1]s" + name = "ratelimit" + enabled = true + + config = jsonencode({ + api_limits = 200 + }) +} +`, env.OS_APIGW_GATEWAY_ID) +} + +func testAccInstanceFeature_basicUpdate() string { + return fmt.Sprintf(` +resource "opentelekomcloud_apigw_gateway_feature_v2" "feat" { + gateway_id = "%[1]s" + name = "ratelimit" + enabled = true + + config = jsonencode({ + api_limits = 300 + }) +} +`, env.OS_APIGW_GATEWAY_ID) +} diff --git a/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2_test.go b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2_test.go new file mode 100644 index 000000000..a28b2b904 --- /dev/null +++ b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2_test.go @@ -0,0 +1,96 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/env" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" +) + +func getInstanceRoutesFunc(cfg *cfg.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := cfg.APIGWV2Client(env.OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating APIG v2 client: %s", err) + } + opts := gateway.ListFeaturesOpts{ + GatewayID: state.Primary.ID, + Limit: 500, + } + resp, err := gateway.ListGatewayFeatures(client, opts) + if err != nil { + return nil, fmt.Errorf("error querying feature list: %s", err) + } + + for _, val := range resp { + if val.Name == "route" { + return val, nil + } + } + return nil, fmt.Errorf("error querying feature: route") +} + +func TestAccInstanceRoutes_basic(t *testing.T) { + var ( + feature gateway.FeatureResp + rName = "opentelekomcloud_apigw_gateway_routes_v2.rt" + ) + + rc := common.InitResourceCheck( + rName, + &feature, + getInstanceRoutesFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + common.TestAccPreCheck(t) + common.TestAccPreCheckApigw(t) + }, + ProviderFactories: common.TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccInstanceRoutes_basic(), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttrPair(rName, "gateway_id", "opentelekomcloud_apigw_gateway_routes_v2.rt", "id"), + resource.TestCheckResourceAttr(rName, "nexthops.#", "2"), + ), + }, + { + Config: testAccInstanceRoutes_basicUpdate(), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(rName, "nexthops.#", "2"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccInstanceRoutes_basic() string { + return fmt.Sprintf(` +resource "opentelekomcloud_apigw_gateway_routes_v2" "rt" { + gateway_id = "%s" + nexthops = ["172.16.128.0/20", "172.16.0.0/20"] +} +`, env.OS_APIGW_GATEWAY_ID) +} + +func testAccInstanceRoutes_basicUpdate() string { + return fmt.Sprintf(` +resource "opentelekomcloud_apigw_gateway_routes_v2" "rt" { + gateway_id = "%s" + nexthops = ["172.16.64.0/20", "172.16.192.0/20"] +} +`, env.OS_APIGW_GATEWAY_ID) +} diff --git a/opentelekomcloud/acceptance/common/common.go b/opentelekomcloud/acceptance/common/common.go index b8473b9df..d89c43145 100644 --- a/opentelekomcloud/acceptance/common/common.go +++ b/opentelekomcloud/acceptance/common/common.go @@ -132,6 +132,12 @@ func TestAccVBSBackupShareCheck(t *testing.T) { } } +func TestAccPreCheckApigw(t *testing.T) { + if env.OS_APIGW_GATEWAY_ID == "" { + t.Skip("Before running APIGW acceptance tests, please ensure the env 'OS_APIGW_GATEWAY_ID' has been configured") + } +} + func TestAccPreCheckServiceAvailability(t *testing.T, service string, regions []string) diag.Diagnostics { t.Logf("Service: %s, Region %s", service, env.OS_REGION_NAME) config := TestAccProvider.Meta().(*cfg.Config) diff --git a/opentelekomcloud/acceptance/env/vars.go b/opentelekomcloud/acceptance/env/vars.go index d81a2a563..92492db0d 100644 --- a/opentelekomcloud/acceptance/env/vars.go +++ b/opentelekomcloud/acceptance/env/vars.go @@ -27,6 +27,7 @@ var ( OS_DC_HOSTING_ID = os.Getenv("OS_DC_HOSTING_ID") OS_DDM_ID = os.Getenv("OS_DDM_ID") OS_RDS_ID = os.Getenv("OS_RDS_ID") + OS_APIGW_GATEWAY_ID = os.Getenv("OS_APIGW_GATEWAY_ID") ) func flavorID() string { diff --git a/opentelekomcloud/common/times.go b/opentelekomcloud/common/times.go index 9192a9745..de16c30ef 100644 --- a/opentelekomcloud/common/times.go +++ b/opentelekomcloud/common/times.go @@ -1,6 +1,9 @@ package common -import "time" +import ( + "log" + "time" +) func FormatTimeStampRFC3339(timestamp int64, isUTC bool, customFormat ...string) string { if timestamp == 0 { @@ -16,3 +19,40 @@ func FormatTimeStampRFC3339(timestamp int64, isUTC bool, customFormat ...string) } return createTime.Format(time.RFC3339) } + +// ConvertTimeStrToNanoTimestamp is a method that used to convert the time string into the corresponding timestamp (in +// nanosecond), e.g. +// The supported time formats are as follows: +// - RFC3339 format: +// 2006-01-02T15:04:05Z (default time format, if you are missing customFormat input) +// 2006-01-02T15:04:05.000000Z +// 2006-01-02T15:04:05Z08:00 +// - Other time formats: +// 2006-01-02 15:04:05 +// 2006-01-02 15:04:05+08:00 +// 2006-01-02T15:04:05 +// ... +// +// Two common uses are shown below: +// - ConvertTimeStrToNanoTimestamp("2024-01-01T00:00:00Z") +// - ConvertTimeStrToNanoTimestamp("2024-01-01T00:00:00+08:00", "2006-01-02T15:04:05Z08:00") +func ConvertTimeStrToNanoTimestamp(timeStr string, customFormat ...string) int64 { + // The default time format is RFC3339. + timeFormat := time.RFC3339 + if len(customFormat) > 0 { + timeFormat = customFormat[0] + } + t, err := time.Parse(timeFormat, timeStr) + if err != nil { + log.Printf("error parsing the input time (%s), the time string does not match time format (%s): %s", + timeStr, timeFormat, err) + return 0 + } + + timestamp := t.UnixNano() / int64(time.Millisecond) + // If the time is less than 1970-01-01T00:00:00Z, the timestamp is negative, such as: "0001-01-01T00:00:00Z" + if timestamp < 0 { + return 0 + } + return timestamp +} diff --git a/opentelekomcloud/provider.go b/opentelekomcloud/provider.go index 78241f8e1..64038b346 100644 --- a/opentelekomcloud/provider.go +++ b/opentelekomcloud/provider.go @@ -261,6 +261,7 @@ func Provider() *schema.Provider { DataSourcesMap: map[string]*schema.Resource{ "opentelekomcloud_antiddos_v1": antiddos.DataSourceAntiDdosV1(), "opentelekomcloud_apigw_api_history_v2": apigw.DataSourceApigwApiHistory(), + "opentelekomcloud_apigw_gateway_features_v2": apigw.DataSourceGatewayFeaturesV2(), "opentelekomcloud_cbr_backup_v3": cbr.DataSourceCBRBackupsV3(), "opentelekomcloud_cbr_backup_ids_v3": cbr.DataSourceCBRBackupsIdsV3(), "opentelekomcloud_cce_cluster_v3": cce.DataSourceCCEClusterV3(), @@ -385,6 +386,8 @@ func Provider() *schema.Provider { "opentelekomcloud_apigw_environment_v2": apigw.ResourceAPIEnvironmentv2(), "opentelekomcloud_apigw_environment_variable_v2": apigw.ResourceAPIEnvironment2Variable(), "opentelekomcloud_apigw_gateway_v2": apigw.ResourceAPIGWv2(), + "opentelekomcloud_apigw_gateway_routes_v2": apigw.ResourceGatewayRoutesV2(), + "opentelekomcloud_apigw_gateway_feature_v2": apigw.ResourceGatewayFeatureV2(), "opentelekomcloud_apigw_group_v2": apigw.ResourceAPIGroupV2(), "opentelekomcloud_apigw_response_v2": apigw.ResourceAPIResponseV2(), "opentelekomcloud_apigw_signature_v2": apigw.ResourceAPISignatureV2(), diff --git a/opentelekomcloud/services/apigw/common.go b/opentelekomcloud/services/apigw/common.go index 888e33e8a..0826b8489 100644 --- a/opentelekomcloud/services/apigw/common.go +++ b/opentelekomcloud/services/apigw/common.go @@ -22,6 +22,9 @@ type ( NetworkType string SecretAction string + RouteConfig struct { + UserRoutes []interface{} `json:"user_routes"` + } ) const ( diff --git a/opentelekomcloud/services/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2.go b/opentelekomcloud/services/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2.go new file mode 100644 index 000000000..46d322cac --- /dev/null +++ b/opentelekomcloud/services/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2.go @@ -0,0 +1,137 @@ +package apigw + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/fmterr" +) + +func DataSourceGatewayFeaturesV2() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceInstanceFeaturesV2Read, + + Schema: map[string]*schema.Schema{ + "gateway_id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "features": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "config": { + Type: schema.TypeString, + Computed: true, + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "region": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceInstanceFeaturesV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + opts := gateway.ListFeaturesOpts{ + GatewayID: d.Get("gateway_id").(string), + // Default value of parameter 'limit' is 20, parameter 'offset' is an invalid parameter. + // If we omit it, we can only obtain 20 features, other features will be lost. + Limit: 500, + } + features, err := gateway.ListGatewayFeatures(client, opts) + if err != nil { + return diag.Errorf("error querying OpenTelekomCloud APIGW v2 gateway feature list: %s", err) + } + + dataSourceId, err := uuid.GenerateUUID() + if err != nil { + return diag.Errorf("unable to generate ID: %s", err) + } + d.SetId(dataSourceId) + + mErr := multierror.Append(nil, + d.Set("region", config.GetRegion(d)), + d.Set("features", filterInstanceFeatures(flattenInstanceFeatures(features), d)), + ) + return diag.FromErr(mErr.ErrorOrNil()) +} + +func filterInstanceFeatures(all []interface{}, d *schema.ResourceData) []interface{} { + rst := make([]interface{}, 0, len(all)) + for _, v := range all { + vMap, ok := v.(map[string]interface{}) + if !ok { + continue + } + if param, ok := d.GetOk("name"); ok { + name, ok := vMap["name"].(string) + if !ok || fmt.Sprint(param) != name { + continue + } + } + rst = append(rst, vMap) + } + return rst +} + +func flattenInstanceFeatures(features []gateway.FeatureResp) []interface{} { + if len(features) < 1 { + return nil + } + + result := make([]interface{}, 0, len(features)) + for _, feature := range features { + updateTime := common.ConvertTimeStrToNanoTimestamp(feature.UpdatedAt) + result = append(result, map[string]interface{}{ + "id": feature.ID, + "name": feature.Name, + "enabled": feature.Enabled, + "config": feature.Config, + // If this feature has not been configured, the time format is "0001-01-01T00:00:00Z", + // the corresponding timestamp is a negative, and this format is uniformly processed as an empty string. + "updated_at": common.FormatTimeStampRFC3339(updateTime/1000, false), + }) + } + return result +} diff --git a/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2.go b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2.go new file mode 100644 index 000000000..33151e55a --- /dev/null +++ b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2.go @@ -0,0 +1,206 @@ +package apigw + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/jmespath/go-jmespath" + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/pointerto" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/fmterr" +) + +func ResourceGatewayFeatureV2() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceInstanceFeatureV2Create, + ReadContext: resourceInstanceFeatureV2Read, + UpdateContext: resourceInstanceFeatureV2Update, + DeleteContext: resourceInstanceFeatureV2Delete, + + Importer: &schema.ResourceImporter{ + StateContext: resourceInstanceFeatureImportState, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "config": { + Type: schema.TypeString, + Optional: true, + }, + "region": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func updateFeatureConfiguration(ctx context.Context, client *golangsdk.ServiceClient, d *schema.ResourceData, gatewayId, name string) (*gateway.FeatureResp, error) { + var resp *gateway.FeatureResp + var reqErr error + err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + resp, reqErr = gateway.ConfigureFeature(client, gateway.FeatureOpts{ + GatewayID: gatewayId, + Name: name, + Enable: pointerto.Bool(d.Get("enabled").(bool)), + Config: d.Get("config").(string), + }) + isRetry, err := handleOperationError409(reqErr) + if isRetry { + // lintignore:R018 + time.Sleep(30 * time.Second) + return resource.RetryableError(err) + } + if err != nil { + return resource.NonRetryableError(err) + } + return nil + }) + return resp, err +} + +func resourceInstanceFeatureV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + gatewayId := d.Get("gateway_id").(string) + name := d.Get("name").(string) + feature, err := updateFeatureConfiguration(ctx, client, d, gatewayId, name) + if err != nil { + return diag.Errorf("error creating OpenTelekomCloud APIGW v2 gateway feature: %s", err) + } + + d.SetId(feature.Name) + clientCtx := common.CtxWithClient(ctx, client, keyClientV2) + return resourceInstanceFeatureV2Read(clientCtx, d, meta) +} + +func handleOperationError409(err error) (bool, error) { + if err == nil { + return false, nil + } + if errCode, ok := err.(golangsdk.ErrUnexpectedResponseCode); ok && errCode.Actual == 409 { + var apiError interface{} + if jsonErr := json.Unmarshal(errCode.Body, &apiError); jsonErr != nil { + return false, jsonErr + } + + errCode, searchErr := jmespath.Search("error_code", apiError) + if searchErr != nil { + return false, err + } + + // APIG.3711: A configuration parameter can be modified only once per minute. + if errCode == "APIG.3711" { + return true, err + } + } + return false, err +} + +func resourceInstanceFeatureV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + gatewayId := d.Get("gateway_id").(string) + features, err := gateway.ListGatewayFeatures(client, gateway.ListFeaturesOpts{ + GatewayID: gatewayId, + Limit: 500, + }) + if err != nil { + // When instance ID not exist, status code is 404, error code id APIG.3030 + return common.CheckDeletedDiag(d, err, "Instance feature configuration") + } + if len(features) < 1 { + return diag.Errorf("error getting OpenTelekomCloud APIGW v2 gateway features: %s", err) + } + var f gateway.FeatureResp + for _, feature := range features { + if feature.Name == d.Id() { + f = feature + } + } + mErr := multierror.Append(nil, + d.Set("name", f.Name), + d.Set("enabled", f.Enabled), + d.Set("config", f.Config), + ) + + return diag.FromErr(mErr.ErrorOrNil()) +} + +func resourceInstanceFeatureV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + gatewayId := d.Get("gateway_id").(string) + _, err = updateFeatureConfiguration(ctx, client, d, gatewayId, d.Id()) + if err != nil { + return diag.Errorf("error creating OpenTelekomCloud APIGW v2 gateway feature: %s", err) + } + if err != nil { + return diag.Errorf("error updating feature (%s) under specified instance (%s): %s", d.Id(), gatewayId, err) + } + clientCtx := common.CtxWithClient(ctx, client, keyClientV2) + return resourceInstanceFeatureV2Read(clientCtx, d, meta) +} + +func resourceInstanceFeatureV2Delete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil +} + +func resourceInstanceFeatureImportState(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + importedId := d.Id() + parts := strings.Split(importedId, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid format specified for import ID, want /, but got '%s'", importedId) + } + + mErr := multierror.Append( + d.Set("gateway_id", parts[0]), + ) + d.SetId(parts[1]) + + return []*schema.ResourceData{d}, mErr.ErrorOrNil() +} diff --git a/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2.go b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2.go new file mode 100644 index 000000000..525026dfb --- /dev/null +++ b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2.go @@ -0,0 +1,178 @@ +package apigw + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/pointerto" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/fmterr" +) + +func ResourceGatewayRoutesV2() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceInstanceRoutesV2Create, + ReadContext: resourceInstanceRoutesV2Read, + UpdateContext: resourceInstanceRoutesV2Update, + DeleteContext: resourceInstanceRoutesV2Delete, + + Importer: &schema.ResourceImporter{ + StateContext: resourceInstanceRoutesImportState, + }, + + Schema: map[string]*schema.Schema{ + "gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "nexthops": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "region": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func modifyInstanceRoutes(client *golangsdk.ServiceClient, gatewayId string, routes []interface{}) error { + routeConfig := map[string]interface{}{ + "user_routes": routes, + } + routeBytes, err := json.Marshal(routeConfig) + if err != nil { + return fmt.Errorf("error parsing routes configuration: %s", err) + } + opts := gateway.FeatureOpts{ + GatewayID: gatewayId, + Name: "route", + Enable: pointerto.Bool(true), + Config: string(routeBytes), + } + log.Printf("[DEBUG] The modify options of the gateway routes is: %#v", opts) + _, err = gateway.ConfigureFeature(client, opts) + if err != nil { + return err + } + return nil +} + +func resourceInstanceRoutesV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + var ( + gatewayId = d.Get("gateway_id").(string) + routes = d.Get("nexthops").(*schema.Set) + ) + if err := modifyInstanceRoutes(client, gatewayId, routes.List()); err != nil { + return diag.Errorf("error creating OpenTelekomCloud APIGW v2 gateway routes: %v", err) + } + d.SetId(gatewayId) + + clientCtx := common.CtxWithClient(ctx, client, keyClientV2) + return resourceInstanceRoutesV2Read(clientCtx, d, meta) +} + +func resourceInstanceRoutesV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + gatewayId := d.Get("gateway_id").(string) + + opts := gateway.ListFeaturesOpts{ + GatewayID: gatewayId, + // Default value of parameter 'limit' is 20, parameter 'offset' is an invalid parameter. + // If we omit it, we can only obtain 20 features, other features will be lost. + Limit: 500, + } + resp, err := gateway.ListGatewayFeatures(client, opts) + if err != nil { + return diag.Errorf("error querying OpenTelekomCloud APIGW v2 gateway feature list: %s", err) + } + log.Printf("[DEBUG] The feature list is: %v", resp) + + var routeConfig string + for _, val := range resp { + if val.Name == "route" { + routeConfig = val.Config + break + } + } + var result RouteConfig + err = json.Unmarshal([]byte(routeConfig), &result) + if err != nil { + return diag.Errorf("error analyzing routes configuration: %s", err) + } + if len(result.UserRoutes) < 1 { + return common.CheckDeletedDiag(d, golangsdk.ErrDefault404{}, "Instance routes") + } + + return diag.FromErr(d.Set("nexthops", result.UserRoutes)) +} + +func resourceInstanceRoutesV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + var ( + gatewayId = d.Get("gateway_id").(string) + routes = d.Get("nexthops").(*schema.Set) + ) + if err := modifyInstanceRoutes(client, gatewayId, routes.List()); err != nil { + return diag.Errorf("error updating OpenTelekomCloud APIGW v2 gateway routes: %v", err) + } + + clientCtx := common.CtxWithClient(ctx, client, keyClientV2) + return resourceInstanceRoutesV2Read(clientCtx, d, meta) +} + +func resourceInstanceRoutesV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) { + return config.APIGWV2Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV2Client, err) + } + + gatewayId := d.Get("gateway_id").(string) + // The expression "{\"user_routes\":null}" has the same result as the expression"{\"user_routes\":[]}". + if err := modifyInstanceRoutes(client, gatewayId, nil); err != nil { + return diag.Errorf("error deleting OpenTelekomCloud APIGW v2 gateway routes: %v", err) + } + + return nil +} + +func resourceInstanceRoutesImportState(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + mErr := multierror.Append(nil, d.Set("gateway_id", d.Id())) + return []*schema.ResourceData{d}, mErr.ErrorOrNil() +} diff --git a/releasenotes/notes/apigw-features-56240ebbc955562c.yaml b/releasenotes/notes/apigw-features-56240ebbc955562c.yaml new file mode 100644 index 000000000..5c1725131 --- /dev/null +++ b/releasenotes/notes/apigw-features-56240ebbc955562c.yaml @@ -0,0 +1,8 @@ +--- +features: + | + **[APIGW]** Add new ``resource/opentelekomcloud_apigw_gateway_routes_v2`` (`#2755 `_) + | + **[APIGW]** Add new ``resource/opentelekomcloud_apigw_gateway_feature_v2`` (`#2755 `_) + | + **[APIGW]** Add new ``data-source/opentelekomcloud_apigw_gateway_features_v2`` (`#2755 `_)