diff --git a/docs/resources/apig_instance.md b/docs/resources/apig_instance.md new file mode 100644 index 0000000000..989596607d --- /dev/null +++ b/docs/resources/apig_instance.md @@ -0,0 +1,111 @@ +--- +subcategory: "API Gateway (APIG)" +--- + +# huaweicloud_apig_instance + +Manages an APIG dedicated instance resource within HuaweiCloud. + +## Example Usage + +```hcl +variable "instance_name" {} +variable "vpc_id" {} +variable "subnet_id" {} +variable "security_group_id" {} +variable "eip_id" {} +variable "enterprise_project_id" {} + +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_apig_instance" "test" { + name = var.instance_name + edition = "BASIC" + vpc_id = var.vpc_id + subnet_id = var.subnet_id + security_group_id = var.security_group_id + enterprise_project_id = var.enterprise_project_id + maintain_begin = "06:00:00" + description = "Created by script" + bandwidth_size = 3 + eip_id = var.eip_id + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + data.huaweicloud_availability_zones.test.names[1], + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region in which to create the APIG dedicated instance resource. + If omitted, the provider-level region will be used. + Changing this will create a new APIG dedicated instance resource. + +* `name` - (Required, String) Specifies the name of the API dedicated instance. + The API group name consists of 3 to 64 characters, starting with a letter. + Only letters, digits, and underscores (_) are allowed. + +* `edition` - (Required, String, ForceNew) Specifies the edition of the APIG dedicated instance. + The supported editions are as follows: + BASIC, PROFESSIONAL, ENTERPRISE, PLATINUM. + Changing this will create a new APIG dedicated instance resource. + +* `vpc_id` - (Required, String, ForceNew) Specifies an ID of the VPC used to create the APIG dedicated instance. + Changing this will create a new APIG dedicated instance resource. + +* `subnet_id` - (Required, String, ForceNew) Specifies an ID of the VPC subnet used to create the APIG dedicated + instance. + Changing this will create a new APIG dedicated instance resource. + +* `security_group_id` - (Required, String) Specifies an ID of the security group to which the APIG dedicated instance + belongs to. + +* `available_zones` - (Optional, List, ForceNew) Specifies an array of available zone names for the APIG dedicated + instance. Please following [reference](https://developer.huaweicloud.com/intl/en-us/endpoint?APIG) for list elements. + Changing this will create a new APIG dedicated instance resource. + +* `description` - (Optional, String) Specifies the description about the APIG dedicated instance. + The description contain a maximum of 255 characters and the angle brackets (< and >) are not allowed. + +* `enterprise_project_id` - (Optional, String, ForceNew) Specifies an enterprise project ID. + This parameter is required for enterprise users. + Changing this will create a new APIG dedicated instance resource. + +* `maintain_begin` - (Optional, String) Specifies a start time of the maintenance time window in the format 'xx:00:00'. + The value of xx can be 02, 06, 10, 14, 18 or 22. + +* `bandwidth_size` - (Optional, Int) Specifies the egress bandwidth size of the APIG dedicated instance. + The range of valid value is from 1 to 2000. + +* `eip_id` - (Optional, String) Specifies the eip ID associated with the APIG dedicated instance. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - ID of the APIG dedicated instance. +* `maintain_end` - End time of the maintenance time window, 4-hour difference between the start time and end time. +* `create_time` - Time when the APIG instance is created, in RFC-3339 format. +* `status` - Status of the APIG dedicated instance. +* `supported_features` - The supported features of the APIG dedicated instance. +* `egress_address` - The egress (nat) public ip address. +* `ingress_address` - The ingress eip address. +* `vpc_ingress_address` - The ingress private ip address of vpc. + +## Timeouts + +This resource provides the following timeouts configuration options: +- `create` - Default is 40 minute. +- `update` - Default is 10 minute. +- `delete` - Default is 10 minute. + +## Import + +APIG Dedicated Instances can be imported by their `id`, e.g. +``` +$ terraform import huaweicloud_apig_instance.test de379eed30aa4d31a84f426ea3c7ef4e +``` diff --git a/go.mod b/go.mod index 40e9839be9..613adf3125 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/go-multierror v1.0.0 github.com/hashicorp/terraform-plugin-sdk v1.16.0 - github.com/huaweicloud/golangsdk v0.0.0-20210621093751-3dd439dd31e3 + github.com/huaweicloud/golangsdk v0.0.0-20210621115823-3cecb9fc9172 github.com/jen20/awspolicyequivalence v1.1.0 github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa // indirect github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 2f28bd1a2f..e05f7481b7 100644 --- a/go.sum +++ b/go.sum @@ -206,8 +206,12 @@ github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/huaweicloud/golangsdk v0.0.0-20210621093751-3dd439dd31e3 h1:RF1TuFSkplWinbZpl6BfVJ+r394k4xOkVKXUt09IjJo= -github.com/huaweicloud/golangsdk v0.0.0-20210621093751-3dd439dd31e3/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= +github.com/huaweicloud/golangsdk v0.0.0-20210608085437-ebb866377c9f h1:uvvd7F4iujOwAP0E0W7H91IrgBt5KI0xE7A6HCwGTj0= +github.com/huaweicloud/golangsdk v0.0.0-20210608085437-ebb866377c9f/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= +github.com/huaweicloud/golangsdk v0.0.0-20210618111817-9c67059b7682 h1:BdVmP7j7kdtRau+JV6QjAMg2sMgsQIbYg9uAqOlt+j8= +github.com/huaweicloud/golangsdk v0.0.0-20210618111817-9c67059b7682/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= +github.com/huaweicloud/golangsdk v0.0.0-20210621115823-3cecb9fc9172 h1:6OLBFIxdht5gTP3AVkKM4CngryJSwRrFGX43xqAPAu4= +github.com/huaweicloud/golangsdk v0.0.0-20210621115823-3cecb9fc9172/go.mod h1:fcOI5u+0f62JtJd7zkCch/Z57BNC6bhqb32TKuiF4r0= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= diff --git a/huaweicloud/config/config.go b/huaweicloud/config/config.go index 144a879f16..9bfce54fc7 100644 --- a/huaweicloud/config/config.go +++ b/huaweicloud/config/config.go @@ -768,6 +768,10 @@ func (c *Config) ApiGatewayV1Client(region string) (*golangsdk.ServiceClient, er return c.NewServiceClient("apig", region) } +func (c *Config) ApigV2Client(region string) (*golangsdk.ServiceClient, error) { + return c.NewServiceClient("apig_v2", region) +} + func (c *Config) BcsV2Client(region string) (*golangsdk.ServiceClient, error) { return c.NewServiceClient("bcs", region) } diff --git a/huaweicloud/config/endpoints.go b/huaweicloud/config/endpoints.go index 639e47ca5e..36dfa4afb5 100644 --- a/huaweicloud/config/endpoints.go +++ b/huaweicloud/config/endpoints.go @@ -309,6 +309,11 @@ var allServiceCatalog = map[string]ServiceCatalog{ ResourceBase: "apigw", WithOutProjectID: true, }, + "apig_v2": { + Name: "apig", + Version: "v2", + ResourceBase: "apigw", + }, "bcs": { Name: "bcs", Version: "v2", diff --git a/huaweicloud/endpoints_test.go b/huaweicloud/endpoints_test.go index 23d2680cbe..87b2caf1e3 100644 --- a/huaweicloud/endpoints_test.go +++ b/huaweicloud/endpoints_test.go @@ -327,6 +327,18 @@ func TestAccServiceEndpoints_Application(t *testing.T) { } t.Logf("API-GW endpoint:\t %s", actualURL) + // test the endpoint of API-GW v2 service + serviceClient, err = config.ApigV2Client(HW_REGION_NAME) + if err != nil { + t.Fatalf("Error creating HuaweiCloud API-GW v2 client: %s", err) + } + expectedURL = fmt.Sprintf("https://apig.%s.%s/v2/apigw/", HW_REGION_NAME, config.Cloud) + actualURL = serviceClient.ResourceBaseURL() + if actualURL != expectedURL { + t.Fatalf("API-GW v2 endpoint: expected %s but got %s", green(expectedURL), yellow(actualURL)) + } + t.Logf("API-GW v2 endpoint:\t %s", actualURL) + // test the endpoint of BCS v2 service serviceClient, err = config.BcsV2Client(HW_REGION_NAME) if err != nil { diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index f309891149..541824542a 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -366,6 +366,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "huaweicloud_api_gateway_api": ResourceAPIGatewayAPI(), "huaweicloud_api_gateway_group": ResourceAPIGatewayGroup(), + "huaweicloud_apig_instance": ResourceApigInstanceV2(), "huaweicloud_as_configuration": ResourceASConfiguration(), "huaweicloud_as_group": ResourceASGroup(), "huaweicloud_as_lifecycle_hook": ResourceASLifecycleHook(), diff --git a/huaweicloud/resource_huaweicloud_apig_instance.go b/huaweicloud/resource_huaweicloud_apig_instance.go new file mode 100644 index 0000000000..e105907761 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_apig_instance.go @@ -0,0 +1,523 @@ +package huaweicloud + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "time" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances" + "github.com/huaweicloud/golangsdk/openstack/networking/v1/eips" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils/fmtp" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils/logp" +) + +type Refresh struct { + Pending, Target []string + Delay, Timeout, MinTimeout, PollInterval time.Duration +} + +func ResourceApigInstanceV2() *schema.Resource { + return &schema.Resource{ + Create: resourceApigInstanceV2Create, + Read: resourceApigInstanceV2Read, + Update: resourceApigInstanceV2Update, + Delete: resourceApigInstanceV2Delete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(40 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile("^([\u4e00-\u9fa5A-Za-z][\u4e00-\u9fa5A-Za-z-_0-9]{2,63})$"), + "The name contains of 3 to 64 characters, starting with a letter. Only letters, digits, "+ + "hyphens (-) and underscore (_) are allowed."), + }, + "edition": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "BASIC", "PROFESSIONAL", "ENTERPRISE", "PLATINUM", + }, false), + }, + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "security_group_id": { + Type: schema.TypeString, + Required: true, + }, + "available_zones": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "enterprise_project_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "bandwidth_size": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 2000), + }, + "eip_id": { + Type: schema.TypeString, + Optional: true, + }, + "maintain_begin": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile("^(02|06|10|14|18|22):00:00$"), + "The start-time format of maintenance window is not 'xx:00:00' or "+ + "the hour is not 02, 06, 10, 14, 18 or 22."), + }, + "ingress_address": { + Type: schema.TypeString, + Computed: true, + }, + "egress_address": { + Type: schema.TypeString, + Computed: true, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + }, + "maintain_end": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "supported_features": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "vpc_ingress_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func buildMaintainEndTime(maintainStart string) (string, error) { + regex := regexp.MustCompile("^(02|06|10|14|18|22):00:00$") + isMatched := regex.MatchString(maintainStart) + if !isMatched { + return "", fmtp.Errorf("The start-time format of maintenance window is not 'xx:00:00' or " + + "the hour is not 02, 06, 10, 14, 18 or 22.") + } + result := regex.FindStringSubmatch(maintainStart) + if len(result) < 2 { + return "", fmtp.Errorf("The hour is missing") + } + num, err := strconv.Atoi(result[1]) + if err != nil { + return "", fmtp.Errorf("The number (%s) cannot be converted to string", result[1]) + } + return fmt.Sprintf("%02d:00:00", (num+4)%24), nil +} + +func buildApigAvailableZones(d *schema.ResourceData) []string { + ids := d.Get("available_zones").([]interface{}) // List of one or more available zone names (codes). + result := make([]string, len(ids)) + for i, v := range ids { + result[i] = v.(string) + } + return result +} + +func buildApigInstanceParameters(d *schema.ResourceData, config *config.Config) (instances.CreateOpts, error) { + opt := instances.CreateOpts{ + Name: d.Get("name").(string), + Edition: d.Get("edition").(string), + VpcId: d.Get("vpc_id").(string), + SubnetId: d.Get("subnet_id").(string), + SecurityGroupId: d.Get("security_group_id").(string), + Description: d.Get("description").(string), + EipId: d.Get("eip_id").(string), + BandwidthSize: d.Get("bandwidth_size").(int), // Bandwidth 0 means turn off the egress access. + EnterpriseProjectId: GetEnterpriseProjectID(d, config), + AvailableZoneIds: buildApigAvailableZones(d), + } + if v, ok := d.GetOk("maintain_begin"); ok { + startTime := v.(string) + opt.MaintainBegin = startTime + endTime, err := buildMaintainEndTime(startTime) + if err != nil { + return opt, err + } + opt.MaintainEnd = endTime + } + + return opt, nil +} + +func watiForApigInstanceV2TargetState(d *schema.ResourceData, client *golangsdk.ServiceClient, ref Refresh) error { + stateConf := &resource.StateChangeConf{ + Pending: ref.Pending, + Target: ref.Target, + Refresh: ApigInstanceV2StateRefreshFunc(client, d.Id()), + Timeout: ref.Timeout, + Delay: ref.Delay, + } + if ref.MinTimeout != 0 { + stateConf.MinTimeout = ref.MinTimeout + } else { + stateConf.PollInterval = ref.PollInterval + } + _, err := stateConf.WaitForState() + return err +} + +func ApigInstanceV2StateRefreshFunc(client *golangsdk.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + opt := instances.ListOpts{ + Id: id, + } + // Some status cannot be read by GET method, just like 'Deleting'. + // GET method will link to other table (vpc) for query. The response time is not as good as the LIST method. + allPages, err := instances.List(client, opt).AllPages() + if err != nil { + return allPages, "", fmtp.Errorf("Error getting APIG v2 dedicated instance by ID (%s): %s", id, err) + } + instances, err := instances.ExtractInstances(allPages) + if len(instances) == 0 { + return instances, "DELETED", nil + } + return instances[0], instances[0].Status, nil + } +} + +func waitForApigInstanceCreateCompleted(d *schema.ResourceData, client *golangsdk.ServiceClient) error { + ref := Refresh{ + Pending: []string{"Creating", "Initing", "Installing", "Registering"}, + Target: []string{"Running"}, + Delay: 30 * time.Second, + Timeout: d.Timeout(schema.TimeoutCreate), + PollInterval: 10 * time.Second, + } + return watiForApigInstanceV2TargetState(d, client, ref) +} + +func resourceApigInstanceV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + opts, err := buildApigInstanceParameters(d, config) + if err != nil { + return fmtp.Errorf("Error craeting APIG v2 dedicated instance options: %s", err) + } + logp.Printf("[DEBUG] Create APIG v2 dedicated instance options: %#v", opts) + + client, err := config.ApigV2Client(GetRegion(d, config)) + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG v2 client: %s", err) + } + v, err := instances.Create(client, opts).Extract() + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG v2 dedicated instance: %s", err) + } + d.SetId(v.Id) + err = waitForApigInstanceCreateCompleted(d, client) + if err != nil { + return fmtp.Errorf("Error waiting for APIG v2 dedicated instance (%s) to become running: %s", d.Id(), err) + } + return resourceApigInstanceV2Read(d, meta) +} + +// Method setApigAvailableZones is used to convert the string returned by the API which contains +// brackets ([ and ]) and space into a list of strings (available_zone code) and save to state. +func setApigAvailableZones(d *schema.ResourceData, resp instances.Instance) error { + codesStr := strings.TrimLeft(resp.AvailableZoneIds, "[") + codesStr = strings.TrimRight(codesStr, "]") + codesStr = strings.ReplaceAll(codesStr, " ", "") + codes := strings.Split(codesStr, ",") + return d.Set("available_zones", codes) +} + +func setApigIngressAccess(d *schema.ResourceData, config *config.Config, resp instances.Instance) error { + if resp.Ipv4IngressEipAddress != "" { + // The response of ingress acess does not contain eip_id, just the ip address. + publicAddress := resp.Ipv4IngressEipAddress + client, err := config.NetworkingV1Client(GetRegion(d, config)) + if err != nil { + return fmtp.Errorf("Error creating VPC client: %s", err) + } + opt := eips.ListOpts{ + PublicIp: publicAddress, + } + allPages, err := eips.List(client, opt).AllPages() + if err != nil { + return err + } + publicIps, err := eips.ExtractPublicIPs(allPages) + if err != nil { + return err + } + if len(publicIps) == 0 { + return fmtp.Errorf("Error getting eip id from server by ip address (%s): %s", publicAddress, err) + } + return d.Set("eip_id", publicIps[0].ID) + } + return d.Set("eip_id", nil) +} + +func setApigSupportedFeatures(d *schema.ResourceData, resp instances.Instance) error { + features := resp.SupportedFeatures + result := make([]interface{}, len(features)) + for i, v := range features { + result[i] = v + } + return d.Set("supported_features", result) +} + +func setApigInstanceParamters(d *schema.ResourceData, config *config.Config, resp instances.Instance) error { + mErr := multierror.Append(nil, + d.Set("region", GetRegion(d, config)), + d.Set("name", resp.Name), + d.Set("edition", resp.Edition), + d.Set("vpc_id", resp.VpcId), + d.Set("subnet_id", resp.SubnetId), + d.Set("security_group_id", resp.SecurityGroupId), + d.Set("maintain_begin", resp.MaintainBegin), + d.Set("maintain_end", resp.MaintainEnd), + d.Set("description", resp.Description), + d.Set("enterprise_project_id", resp.EnterpriseProjectId), + d.Set("status", resp.Status), + d.Set("bandwidth_size", resp.BandwidthSize), + d.Set("vpc_ingress_address", resp.Ipv4VpcIngressAddress), + d.Set("egress_address", resp.Ipv4EgressAddress), + d.Set("ingress_address", resp.Ipv4IngressEipAddress), + setApigAvailableZones(d, resp), + d.Set("create_time", utils.FormatTimeStampRFC3339(resp.CreateTimestamp)), + setApigIngressAccess(d, config, resp), + setApigSupportedFeatures(d, resp), + ) + if mErr.ErrorOrNil() != nil { + return mErr + } + return nil +} + +func getApigInstanceFromServer(d *schema.ResourceData, client *golangsdk.ServiceClient) (*instances.Instance, error) { + resp, err := instances.Get(client, d.Id()).Extract() + if err != nil { + return resp, CheckDeleted(d, err, "APIG v2 dedicated instance") + } + logp.Printf("[DEBUG] Retrieved APIG v2 dedicated instance (%s): %+v", d.Id(), resp) + return resp, nil +} + +func resourceApigInstanceV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + client, err := config.ApigV2Client(GetRegion(d, config)) + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG client: %s", err) + } + resp, err := getApigInstanceFromServer(d, client) + if err != nil { + return fmtp.Errorf("Error getting APIG v2 dedicated instance (%s) form server: %s", d.Id(), err) + } + return setApigInstanceParamters(d, config, *resp) +} + +func buildApigInstanceUpdateOpts(d *schema.ResourceData) (instances.UpdateOpts, error) { + opts := instances.UpdateOpts{} + if d.HasChange("name") { + opts.Name = d.Get("name").(string) + } + if d.HasChange("description") { + opts.Description = d.Get("description").(string) + } + if d.HasChange("maintain_begin") { + startTime := d.Get("maintain_begin").(string) + opts.MaintainBegin = startTime + endTime, err := buildMaintainEndTime(startTime) + if err != nil { + return opts, err + } + opts.MaintainEnd = endTime + } + if d.HasChange("security_group_id") { + opts.SecurityGroupId = d.Get("security_group_id").(string) + } + logp.Printf("[DEBUG] Update options of APIG v2 dedicated instance is: %#v", opts) + return opts, nil +} + +func waitForApigInstanceUpdateCompleted(d *schema.ResourceData, client *golangsdk.ServiceClient) error { + ref := Refresh{ + Pending: []string{"Updating", "Running"}, + Target: []string{"Running"}, + Delay: 2 * time.Second, + Timeout: d.Timeout(schema.TimeoutUpdate), + MinTimeout: 2 * time.Second, + } + return watiForApigInstanceV2TargetState(d, client, ref) +} + +func updateApigInstanceEgressAccess(d *schema.ResourceData, client *golangsdk.ServiceClient) error { + oldVal, newVal := d.GetChange("bandwidth_size") + // Enable the egress access. + if oldVal.(int) == 0 { + size := d.Get("bandwidth_size").(int) + opts := instances.EgressAccessOpts{ + BandwidthSize: strconv.Itoa(size), + } + egress, err := instances.EnableEgressAccess(client, d.Id(), opts).Extract() + if err != nil { + return fmtp.Errorf("Unable to enable egress access of the dedicated instance (%s), size: %d", d.Id(), size) + } + if egress.BandwidthSize != size { + return fmtp.Errorf("Wrong bandwidth size is enabled, size: %d", size) + } + } + // Disable the egress access. + if newVal.(int) == 0 { + err := instances.DisableEgressAccess(client, d.Id()).ExtractErr() + if err != nil { + return fmtp.Errorf("Unable to disable egress bandwidth of the dedicated instance (%s)", d.Id()) + } + return nil + } + // Update the egress nat. + size := d.Get("bandwidth_size").(int) + opts := instances.EgressAccessOpts{ + BandwidthSize: strconv.Itoa(size), + } + egress, err := instances.UpdateEgressBandwidth(client, d.Id(), opts).Extract() + if err != nil { + return fmtp.Errorf("Unable to update egress bandwidth of the dedicated instance (%s), size: %d", d.Id(), size) + } + if egress.BandwidthSize != size { + return fmtp.Errorf("Wrong bandwidth size is set, size: %d", size) + } + return nil +} + +func updateApigInstanceIngressAccess(d *schema.ResourceData, client *golangsdk.ServiceClient) (err error) { + oldVal, newVal := d.GetChange("eip_id") + // Disable the ingress access. + // The update logic is to disable first and then enable. Update means thar both oldVal and newVal exist. + if oldVal.(string) != "" { + err = instances.DisableIngressAccess(client, d.Id()).ExtractErr() + if err != nil || newVal.(string) == "" { + return + } + } + // Enable the ingress access. + updateOpts := instances.IngressAccessOpts{ + EipId: d.Get("eip_id").(string), + } + _, err = instances.EnableIngressAccess(client, d.Id(), updateOpts).Extract() + return +} + +func disableApigInstanceIngressAccess(d *schema.ResourceData, client *golangsdk.ServiceClient) error { + return instances.DisableIngressAccess(client, d.Id()).ExtractErr() +} + +func resourceApigInstanceV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + client, err := config.ApigV2Client(GetRegion(d, config)) + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG v2 client: %s", err) + } + + // Update egress access + if d.HasChange("bandwidth_size") { + if err = updateApigInstanceEgressAccess(d, client); err != nil { + return fmtp.Errorf("Update egress access failed: %s", err) + } + } + // Update ingerss access + if d.HasChange("eip_id") { + if err = updateApigInstanceIngressAccess(d, client); err != nil { + return fmtp.Errorf("Update ingress access failed: %s", err) + } + } + // Update APIG v2 instance name, maintain window, description and security group id + updateOpts, err := buildApigInstanceUpdateOpts(d) + if err != nil { + return fmtp.Errorf("Unable to get the update options of APIG v2 dedicated instance: %s", err) + } + if updateOpts != (instances.UpdateOpts{}) { + _, err = instances.Update(client, d.Id(), updateOpts).Extract() + if err != nil { + return fmtp.Errorf("Error updating APIG v2 dedicated instance: %s", err) + } + err = waitForApigInstanceUpdateCompleted(d, client) + if err != nil { + return fmtp.Errorf("Error waiting for APIG dedicated instance (%s) to become running: %s", d.Id(), err) + } + } + return resourceApigInstanceV2Read(d, meta) +} + +func waitForApigInstanceDeleteCompleted(d *schema.ResourceData, client *golangsdk.ServiceClient) error { + ref := Refresh{ + Pending: []string{"Deleting"}, + Target: []string{"DELETED"}, + Delay: 30 * time.Second, + Timeout: d.Timeout(schema.TimeoutDelete), + PollInterval: 10 * time.Second, + } + return watiForApigInstanceV2TargetState(d, client, ref) +} + +func resourceApigInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*config.Config) + client, err := config.ApigV2Client(GetRegion(d, config)) + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG v2 client: %s", err) + } + if err = instances.Delete(client, d.Id()).ExtractErr(); err != nil { + return fmtp.Errorf("Unable to delete the APIG v2 dedicated instance (%s): %s", d.Id(), err) + } + err = waitForApigInstanceDeleteCompleted(d, client) + if err != nil { + return fmtp.Errorf("Error deleting APIG v2 dedicated instance (%s): %s", d.Id(), err) + } + d.SetId("") + return nil +} diff --git a/huaweicloud/resource_huaweicloud_apig_instance_test.go b/huaweicloud/resource_huaweicloud_apig_instance_test.go new file mode 100644 index 0000000000..a6e5fd57ba --- /dev/null +++ b/huaweicloud/resource_huaweicloud_apig_instance_test.go @@ -0,0 +1,426 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils/fmtp" +) + +func TestAccApigInstanceV2_basic(t *testing.T) { + var resourceName = "huaweicloud_apig_instance.test" + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + var instance instances.Instance + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckEpsID(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckApigInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccApigInstance_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "edition", "BASIC"), + resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", HW_ENTERPRISE_PROJECT_ID_TEST), + resource.TestCheckResourceAttr(resourceName, "maintain_begin", "14:00:00"), + resource.TestCheckResourceAttr(resourceName, "maintain_end", "18:00:00"), + resource.TestCheckResourceAttr(resourceName, "description", "created by acc test"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + ), + }, + { + Config: testAccApigInstance_update(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName+"-update"), + resource.TestCheckResourceAttr(resourceName, "edition", "BASIC"), + resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", HW_ENTERPRISE_PROJECT_ID_TEST), + resource.TestCheckResourceAttr(resourceName, "maintain_begin", "18:00:00"), + resource.TestCheckResourceAttr(resourceName, "maintain_end", "22:00:00"), + resource.TestCheckResourceAttr(resourceName, "description", "updated by acc test"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccApigInstanceV2_egress(t *testing.T) { + var resourceName = "huaweicloud_apig_instance.test" + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + var instance instances.Instance + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckEpsID(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckApigInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccApigInstance_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "edition", "BASIC"), + resource.TestCheckResourceAttr(resourceName, "maintain_begin", "14:00:00"), + resource.TestCheckResourceAttr(resourceName, "description", "created by acc test"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + resource.TestCheckResourceAttr(resourceName, "bandwidth_size", "0"), + ), + }, + { + Config: testAccApigInstance_egress(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + resource.TestCheckResourceAttr(resourceName, "bandwidth_size", "3"), + resource.TestCheckResourceAttrSet(resourceName, "egress_address"), + ), + }, + { + Config: testAccApigInstance_egressUpdate(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + resource.TestCheckResourceAttr(resourceName, "bandwidth_size", "5"), + resource.TestCheckResourceAttrSet(resourceName, "egress_address"), + ), + }, + { + Config: testAccApigInstance_basic(rName), // Unbind egress nat + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + resource.TestCheckResourceAttr(resourceName, "bandwidth_size", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccApigInstanceV2_ingress(t *testing.T) { + var resourceName = "huaweicloud_apig_instance.test" + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + var instance instances.Instance + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckEpsID(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckApigInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccApigInstance_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "edition", "BASIC"), + resource.TestCheckResourceAttr(resourceName, "maintain_begin", "14:00:00"), + resource.TestCheckResourceAttr(resourceName, "description", "created by acc test"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + ), + }, + { + Config: testAccApigInstance_ingress(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + resource.TestCheckResourceAttrSet(resourceName, "eip_id"), + resource.TestCheckResourceAttrSet(resourceName, "ingress_address"), + ), + }, + { + Config: testAccApigInstance_ingressUpdate(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + resource.TestCheckResourceAttrSet(resourceName, "eip_id"), + resource.TestCheckResourceAttrSet(resourceName, "ingress_address"), + ), + }, + { + Config: testAccApigInstance_basic(rName), // Unbind ingress eip + Check: resource.ComposeTestCheckFunc( + testAccCheckApigInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrSet(resourceName, "vpc_ingress_address"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckApigInstanceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*config.Config) + client, err := config.ApigV2Client(HW_REGION_NAME) + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG v2 client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_apig_instance" { + continue + } + _, err := instances.Get(client, rs.Primary.ID).Extract() + if err == nil { + return fmtp.Errorf("APIG v2 instance (%s) is still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckApigInstanceExists(n string, instance *instances.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmtp.Errorf("Resource %s not found", n) + } + if rs.Primary.ID == "" { + return fmtp.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*config.Config) + client, err := config.ApigV2Client(HW_REGION_NAME) + if err != nil { + return fmtp.Errorf("Error creating HuaweiCloud APIG v2 client: %s", err) + } + + found, err := instances.Get(client, rs.Primary.ID).Extract() + if err != nil { + return err + } + *instance = *found + return nil + } +} + +func testAccApigInstance_base(rName string) string { + return fmt.Sprintf(` +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_vpc" "test" { + name = "%s" + cidr = "192.168.0.0/16" +} + +resource "huaweicloud_vpc_subnet" "test" { + name = "%s" + vpc_id = huaweicloud_vpc.test.id + gateway_ip = "192.168.0.1" + cidr = "192.168.0.0/24" +} +`, rName, rName) +} + +func testAccApigInstance_basic(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_networking_secgroup" "test" { + name = "%s" +} + +resource "huaweicloud_apig_instance" "test" { + name = "%s" + edition = "BASIC" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + enterprise_project_id = "%s" + maintain_begin = "14:00:00" + description = "created by acc test" + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + ] +} +`, testAccApigInstance_base(rName), rName, rName, HW_ENTERPRISE_PROJECT_ID_TEST) +} + +func testAccApigInstance_update(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_networking_secgroup" "update" { + name = "%s-update" +} + +resource "huaweicloud_apig_instance" "test" { + name = "%s-update" + edition = "BASIC" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.update.id + enterprise_project_id = "%s" + maintain_begin = "18:00:00" + description = "updated by acc test" + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + ] +} +`, testAccApigInstance_base(rName), rName, rName, HW_ENTERPRISE_PROJECT_ID_TEST) +} + +func testAccApigInstance_egress(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_networking_secgroup" "test" { + name = "%s" +} + +resource "huaweicloud_apig_instance" "test" { + name = "%s" + edition = "BASIC" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + enterprise_project_id = "%s" + maintain_begin = "14:00:00" + description = "created by acc test" + bandwidth_size = 3 + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + ] +} +`, testAccApigInstance_base(rName), rName, rName, HW_ENTERPRISE_PROJECT_ID_TEST) +} + +func testAccApigInstance_egressUpdate(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_networking_secgroup" "test" { + name = "%s" +} + +resource "huaweicloud_apig_instance" "test" { + name = "%s" + edition = "BASIC" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + enterprise_project_id = "%s" + maintain_begin = "14:00:00" + description = "created by acc test" + bandwidth_size = 5 + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + ] +} +`, testAccApigInstance_base(rName), rName, rName, HW_ENTERPRISE_PROJECT_ID_TEST) +} + +func testAccApigInstance_ingress(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_vpc_eip" "test" { + publicip { + type = "5_bgp" + } + bandwidth { + name = "%s" + size = 3 + share_type = "PER" + charge_mode = "traffic" + } +} + +resource "huaweicloud_networking_secgroup" "test" { + name = "%s" +} + +resource "huaweicloud_apig_instance" "test" { + name = "%s" + edition = "BASIC" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + enterprise_project_id = "%s" + maintain_begin = "14:00:00" + description = "created by acc test" + eip_id = huaweicloud_vpc_eip.test.id + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + ] +} +`, testAccApigInstance_base(rName), rName, rName, rName, HW_ENTERPRISE_PROJECT_ID_TEST) +} + +func testAccApigInstance_ingressUpdate(rName string) string { + return fmt.Sprintf(` +%s + +resource "huaweicloud_vpc_eip" "update" { + publicip { + type = "5_bgp" + } + bandwidth { + name = "%s-update" + size = 4 + share_type = "PER" + charge_mode = "traffic" + } +} + +resource "huaweicloud_networking_secgroup" "test" { + name = "%s" +} + +resource "huaweicloud_apig_instance" "test" { + name = "%s" + edition = "BASIC" + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + enterprise_project_id = "%s" + maintain_begin = "14:00:00" + description = "created by acc test" + eip_id = huaweicloud_vpc_eip.update.id + + available_zones = [ + data.huaweicloud_availability_zones.test.names[0], + ] +} +`, testAccApigInstance_base(rName), rName, rName, rName, HW_ENTERPRISE_PROJECT_ID_TEST) +} diff --git a/huaweicloud/utils/utils.go b/huaweicloud/utils/utils.go index 2e36ea9294..86f493c78d 100644 --- a/huaweicloud/utils/utils.go +++ b/huaweicloud/utils/utils.go @@ -7,6 +7,7 @@ import ( "log" "regexp" "strings" + "time" "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" "github.com/huaweicloud/golangsdk" @@ -225,3 +226,9 @@ func IsResourceNotFound(err error) bool { _, ok := err.(golangsdk.ErrDefault404) return ok } + +// Method FormatTimeStampRFC3339 is used to unify the time format to RFC-3339 and return a time string. +func FormatTimeStampRFC3339(timestamp int64) string { + createTime := time.Unix(timestamp, 0) + return createTime.Format(time.RFC3339) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/requests.go new file mode 100644 index 0000000000..6acc2d8f0b --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/requests.go @@ -0,0 +1,238 @@ +package instances + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// CreateOpts allows to create an APIG dedicated instance using given parameters. +type CreateOpts struct { + // Name of the APIG dedicated instance. The name can contains of 3 to 64 characters. + Name string `json:"instance_name" required:"true"` + // Edition of the APIG dedicated instance. Currently, the editions are support: + // (IPv4): BASIC, PROFESSIONAL, ENTERPRISE, PLATINUM + // (IPv6): BASIC_IPV6, PROFESSIONAL_IPV6, ENTERPRISE_IPV6, PLATINUM_IPV6 + Edition string `json:"spec_id" required:"true"` + // VPC ID. + VpcId string `json:"vpc_id" required:"true"` + // Subnet network ID. + SubnetId string `json:"subnet_id" required:"true"` + // ID of the security group to which the APIG dedicated instance belongs to. + SecurityGroupId string `json:"security_group_id" required:"true"` + // ID of the APIG dedicated instance, which will be automatically generated if you do not specify this parameter. + Id string `json:"instance_id,omitempty"` + // Description about the APIG dedicated instance. + Description string `json:"description,omitempty"` + // Start time of the maintenance time window in the format "xx:00:00". + // The value of xx can be 02, 06, 10, 14, 18, or 22. + MaintainBegin string `json:"maintain_begin,omitempty"` + // End time of the maintenance time window in the format "xx:00:00". + // There is a 4-hour difference between the start time and end time. + MaintainEnd string `json:"maintain_end,omitempty"` + // EIP ID. + EipId string `json:"eip_id,omitempty"` + // Outbound access bandwidth. This parameter is required if public outbound access is enabled for the APIG + // dedicated instance. + // Zero means turn off the egress access. + BandwidthSize int `json:"bandwidth_size"` + // Enterprise project ID. This parameter is required if you are using an enterprise account. + EnterpriseProjectId string `json:"enterprise_project_id,omitempty"` + // AZs. + AvailableZoneIds []string `json:"available_zone_ids,omitempty"` + // Whether public access with an IPv6 address is supported. + Ipv6Enable bool `json:"ipv6_enable,omitempty"` +} + +type CreateOptsBuilder interface { + ToInstanceCreateMap() (map[string]interface{}, error) +} + +func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// Create is a method by which to create function that create a APIG dedicated instance. +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + reqBody, err := opts.ToInstanceCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(rootURL(client), reqBody, &r.Body, nil) + return +} + +// Get is a method to obtain the specified APIG dedicated instance according to the instance Id. +func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(resourceURL(client, id), &r.Body, nil) + return +} + +// ListOpts allows to filter list data using given parameters. +type ListOpts struct { + // ID of the APIG dedicated instance. + Id string `q:"instance_id"` + // Name of the APIG dedicated instance. + Name string `q:"instance_name"` + // Instance status. + Status string `q:"status"` + // Offset from which the query starts. + // If the offset is less than 0, the value is automatically converted to 0. Default to 0. + Offset int `q:"offset"` + // Number of items displayed on each page. + Limit int `q:"limit"` +} + +type ListOptsBuilder interface { + ToInstanceListQuery() (string, error) +} + +func (opts ListOpts) ToInstanceListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), err +} + +// List is a method to obtain an array of one or more APIG dedicated instance according to the query parameters. +func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(client) + if opts != nil { + query, err := opts.ToInstanceListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return InstancePage{pagination.SinglePageBase(r)} + }) +} + +// UpdateOpts allows to update an existing APIG dedicated instance using given parameters. +type UpdateOpts struct { + // Description about the APIG dedicated instance. + Description string `json:"description,omitempty"` + // Start time of the maintenance time window in the format "xx:00:00". + // The value of xx can be 02, 06, 10, 14, 18, or 22. + MaintainBegin string `json:"maintain_begin,omitempty"` + // End time of the maintenance time window in the format "xx:00:00". + // There is a 4-hour difference between the start time and end time. + MaintainEnd string `json:"maintain_end,omitempty"` + // Description about the APIG dedicated instance. + Name string `json:"instance_name,omitempty"` + // ID of the security group to which the APIG dedicated instance belongs to. + SecurityGroupId string `json:"security_group_id,omitempty"` +} + +type UpdateOptsBuilder interface { + ToInstanceUpdateMap() (map[string]interface{}, error) +} + +func (opts UpdateOpts) ToInstanceUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// Update is a method by which to update an existing APIG dedicated instance. +func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + reqBody, err := opts.ToInstanceUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(resourceURL(client, id), reqBody, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete is a method to delete an existing APIG dedicated instance +func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { + _, r.Err = client.Delete(resourceURL(client, id), nil) + return +} + +// EgressAccessOpts allows the bandwidth size of an existing APIG dedicated instance to be updated with the given +// parameters. +type EgressAccessOpts struct { + // Outbound access bandwidth, in Mbit/s. + BandwidthSize string `json:"bandwidth_size,omitempty"` +} + +type EgressAccessOptsBuilder interface { + ToEgressAccessMap() (map[string]interface{}, error) +} + +func (opts EgressAccessOpts) ToEgressAccessMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// EnableEgressAccess is a method by which to enable the egress access of an existing APIG dedicated instance. +func EnableEgressAccess(client *golangsdk.ServiceClient, id string, opts EgressAccessOptsBuilder) (r EnableEgressResult) { + reqBody, err := opts.ToEgressAccessMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(egressURL(client, id), reqBody, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return +} + +// UpdateEgressBandwidth is a method by which to update the egress bandwidth size of an existing APIG dedicated instance. +func UpdateEgressBandwidth(client *golangsdk.ServiceClient, id string, opts EgressAccessOptsBuilder) (r UdpateEgressResult) { + reqBody, err := opts.ToEgressAccessMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(egressURL(client, id), reqBody, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DisableEgressAccess is a method by which to disable the egress access of an existing APIG dedicated instance. +func DisableEgressAccess(client *golangsdk.ServiceClient, id string) (r DisableEgressResult) { + _, r.Err = client.Delete(egressURL(client, id), nil) + return +} + +// IngressAccessOpts allows binding and updating the eip associated with an existing APIG dedicated instance with the +// given parameters. +type IngressAccessOpts struct { + // EIP ID + EipId string `json:"eip_id,omitempty"` +} + +type IngressAccessOptsBuilder interface { + ToEnableIngressAccessMap() (map[string]interface{}, error) +} + +func (opts IngressAccessOpts) ToEnableIngressAccessMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// UpdateIngressAccess is a method to bind and update the eip associated with an existing APIG dedicated instance. +func EnableIngressAccess(client *golangsdk.ServiceClient, id string, opts IngressAccessOptsBuilder) (r EnableIngressResult) { + reqBody, err := opts.ToEnableIngressAccessMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(ingressURL(client, id), reqBody, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// DisableIngressAccess is a method to unbind the eip associated with an existing APIG dedicated instance. +func DisableIngressAccess(client *golangsdk.ServiceClient, id string) (r DisableIngressResult) { + _, r.Err = client.Delete(ingressURL(client, id), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/results.go new file mode 100644 index 0000000000..462fd8b0a5 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/results.go @@ -0,0 +1,242 @@ +package instances + +import ( + "encoding/json" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type commonResult struct { + golangsdk.Result +} + +// GetResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +type CreateResp struct { + Id string `json:"instance_id"` + Message string `json:"message"` +} + +//Call its Extract method to interpret it as a Instance Id. +func (r CreateResult) Extract() (*CreateResp, error) { + var s CreateResp + err := r.ExtractInto(&s) + return &s, err +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + commonResult +} + +// UpdateResult represents the result of a Update operation. +type UpdateResult struct { + commonResult +} + +type Instance struct { + // Instance ID. + Id string `json:"id"` + // Project ID. + ProjectId string `json:"project_id"` + // Instance name. + Name string `json:"instance_name"` + // Instance status. The value are as following: + // Creating, CreateSuccess, CreateFail, Initing, Registering, Running, InitingFailed, RegisterFailed, Installing + // InstallFailed, Updating, UpdateFailed, Rollbacking, RollbackSuccess, RollbackFailed, Deleting, DeleteFailed + // Unregistering, UnRegisterFailed, CreateTimeout, InitTimeout, RegisterTimeout, InstallTimeout, UpdateTimeout + // RollbackTimeout, DeleteTimeout, UnregisterTimeout, Starting, Freezing, Frozen, Restarting, RestartFail + // Unhealthy, RestartTimeout + // The status 'Deleting' is not supported, it's a BUG. --2021/06/15 + Status string `json:"status"` + // Instance status ID. + // 1:Creating, 2:CreateSuccess, 3:CreateFail, 4:Initing, 5:Registering, 6:Running, 7:InitingFailed + // 8:RegisterFailed, 10:Installing, 11:InstallFailed, 12:Updating, 13:UpdateFailed, 20:Rollbacking + // 21:RollbackSuccess, 22:RollbackFailed, 23:Deleting, 24:DeleteFailed, 25:Unregistering, 26:UnRegisterFailed + // 27:CreateTimeout, 28:InitTimeout, 29:RegisterTimeout, 30:InstallTimeout, 31:UpdateTimeout + // 32:RollbackTimeout, 33:DeleteTimeout, 34:UnregisterTimeout, 35:Starting, 36:Freezing, 37:Frozen, 38:Restarting + // 39:RestartFail, 40:Unhealthy, 41:RestartTimeout + // Ditto: Issue of status id 23 (Deleting). --2021/06/15 + StatusId int `json:"instance_status"` + // Instance type. + Type string `json:"type"` + // Instance edition. + Edition string `json:"spec"` + // Time when the APIG dedicated instance is created, in Unix timestamp format. + CreateTimestamp int64 `json:"create_time"` + // Enterprise project ID. + EnterpriseProjectId string `json:"enterprise_project_id"` + // Billing mode of the APIG dedicated instance. + // 0:Pay per use + // 1:Pay per use + ChargeMode int `json:"charging_mode"` + // Yearly/Monthly subscription order ID. + CbcMetadata string `json:"cbc_metadata"` + // Description about the APIG dedicated instance. + Description string `json:"description"` + // VPC ID. + VpcId string `json:"vpc_id"` + // Subnet network ID. + SubnetId string `json:"subnet_id"` + // ID of the security group to which the APIG dedicated instance belongs to. + SecurityGroupId string `json:"security_group_id"` + // Start time of the maintenance time window in the format "xx:00:00". + MaintainBegin string `json:"maintain_begin"` + // End time of the maintenance time window in the format "xx:00:00". + MaintainEnd string `json:"maintain_end"` + // VPC ingress private address. + Ipv4VpcIngressAddress string `json:"ingress_ip"` + // VPC ingress private address (IPv6). + Ipv6VpcIngressAddress string `json:"ingress_ip_v6"` + // ID of the account to which the APIG dedicated instance belongs. + UserId string `json:"user_id"` + // EIP bound to the APIG dedicated instance. + Ipv4IngressEipAddress string `json:"eip_address"` + // EIP (IPv6). + Ipv6IngressEipAddress string `json:"eip_ipv6_address"` + // Public egress address (IPv6). + Ipv6EgressCidr string `json:"nat_eip_ipv6_cidr"` + // IP address for public outbound access. + Ipv4EgressAddress string `json:"nat_eip_address"` + // Outbound access bandwidth. + BandwidthSize int `json:"bandwidth_size"` + // AZs. + AvailableZoneIds string `json:"available_zone_ids"` + // Instance version. + Version string `json:"instance_version"` + // Supported features. + SupportedFeatures []string `json:"supported_features"` +} + +// Call its Extract method to interpret it as a Instance. +func (r commonResult) Extract() (*Instance, error) { + var s Instance + err := r.ExtractInto(&s) + return &s, err +} + +type BaseInstance struct { + // Instance ID. + Id string `json:"id"` + // Project ID + ProjectId string `json:"project_id"` + // Instance name. + Name string `json:"instance_name"` + // Instance status. The value are as following: + // Creating, CreateSuccess, CreateFail, Initing, Registering, Running, InitingFailed, RegisterFailed, Installing + // InstallFailed, Updating, UpdateFailed, Rollbacking, RollbackSuccess, RollbackFailed, Deleting, DeleteFailed + // Unregistering, UnRegisterFailed, CreateTimeout, InitTimeout, RegisterTimeout, InstallTimeout, UpdateTimeout + // RollbackTimeout, DeleteTimeout, UnregisterTimeout, Starting, Freezing, Frozen, Restarting, RestartFail + // Unhealthy, RestartTimeout + // Ditto: Issue of status 'Deleting'. --2021/06/15 + Status string `json:"status"` + // Instance status ID. + // 1:Creating, 2:CreateSuccess, 3:CreateFail, 4:Initing, 5:Registering, 6:Running, 7:InitingFailed + // 8:RegisterFailed, 10:Installing, 11:InstallFailed, 12:Updating, 13:UpdateFailed, 20:Rollbacking + // 21:RollbackSuccess, 22:RollbackFailed, 23:Deleting, 24:DeleteFailed, 25:Unregistering, 26:UnRegisterFailed + // 27:CreateTimeout, 28:InitTimeout, 29:RegisterTimeout, 30:InstallTimeout, 31:UpdateTimeout + // 32:RollbackTimeout, 33:DeleteTimeout, 34:UnregisterTimeout, 35:Starting, 36:Freezing, 37:Frozen, 38:Restarting + // 39:RestartFail, 40:Unhealthy, 41:RestartTimeout + // Ditto: Issue of status id 23 (Deleting). --2021/06/15 + StatusId int `json:"instance_status"` + // Instance type. + Type string `json:"type"` + // Instance edition. + Edition string `json:"spec"` + // Time when the APIG dedicated instance is created, in Unix timestamp format. + CreateTimestamp int64 `json:"create_time"` + // Enterprise project ID. + EnterpriseProjectId string `json:"enterprise_project_id"` + // EIP bound to the APIG dedicated instance. + Ipv4Address string `json:"eip_address"` + // Billing mode of the APIG dedicated instance. + // 0:Pay per use + // 1:Pay per use + ChargeMode int `json:"charging_mode"` + // Yearly/Monthly subscription order ID. + CbcMetadata string `json:"cbc_metadata"` +} + +// InstancePage represents the result of a List operation. +type InstancePage struct { + pagination.SinglePageBase +} + +// Call its Extract method to interpret it as a BaseInstance array. +func ExtractInstances(r pagination.Page) ([]BaseInstance, error) { + var s []BaseInstance + err := r.(InstancePage).Result.ExtractIntoSlicePtr(&s, "instances") + return s, err +} + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + golangsdk.ErrResult +} + +// EnableEgressResult represents the result of a EnableEgressAccess operation. +type EnableEgressResult struct { + EgressResult +} + +// UdpateEgressResult represents the result of a UpdateEgressBandwidth operation. +type UdpateEgressResult struct { + EgressResult +} + +type EgressResult struct { + golangsdk.Result +} + +type Egress struct { + Id string `json:"id"` + CloudEipId string `json:"cloudEipId"` + CloudEipAddress string `json:"cloudEipAddress"` + InstanceId string `json:"instanceId"` + CloudBandwidthId string `json:"cloudBandwidthId"` + BandwidthName string `json:"bandwidthName"` + BandwidthSize int `json:"bandwidthSize"` +} + +// Call its Extract method to interpret it as a Egress. +func (r EgressResult) Extract() (*Egress, error) { + var s Egress + if r.Err != nil { + return &s, r.Err + } + err := json.Unmarshal([]byte(r.Body.(string)), &s) + return &s, err +} + +// DisableEgressResult represents the result of a DisableEgressAccess operation. +type DisableEgressResult struct { + golangsdk.ErrResult +} + +// EnableIngressResult represents the result of a EnableIngressAccess operation. +type EnableIngressResult struct { + commonResult +} + +type Ingress struct { + Id string `json:"eip_id"` + EipAddress string `json:"eip_address"` + Status string `json:"eip_status"` + Ipv6Address string `json:"eip_ipv6_address"` +} + +// Call its Extract method to interpret it as a Ingress. +func (r EnableIngressResult) Extract() (*Ingress, error) { + var s Ingress + err := r.ExtractInto(&s) + return &s, err +} + +// DisableIngressResult represents the result of a DisableIngressAccess operation. +type DisableIngressResult struct { + golangsdk.ErrResult +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/urls.go new file mode 100644 index 0000000000..a9bb966be1 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances/urls.go @@ -0,0 +1,21 @@ +package instances + +import "github.com/huaweicloud/golangsdk" + +const rootPath = "instances" + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} + +func egressURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, "nat-eip") +} + +func ingressURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, "eip") +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 12cd320002..3e31b59ebd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -267,7 +267,7 @@ github.com/hashicorp/terraform-svchost/auth github.com/hashicorp/terraform-svchost/disco # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux -# github.com/huaweicloud/golangsdk v0.0.0-20210621093751-3dd439dd31e3 +# github.com/huaweicloud/golangsdk v0.0.0-20210621115823-3cecb9fc9172 ## explicit github.com/huaweicloud/golangsdk github.com/huaweicloud/golangsdk/internal @@ -276,6 +276,7 @@ github.com/huaweicloud/golangsdk/openstack/antiddos/v1/antiddos github.com/huaweicloud/golangsdk/openstack/aom/v1/icagents github.com/huaweicloud/golangsdk/openstack/apigw/apis github.com/huaweicloud/golangsdk/openstack/apigw/groups +github.com/huaweicloud/golangsdk/openstack/apigw/v2/instances github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/configurations github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/groups github.com/huaweicloud/golangsdk/openstack/autoscaling/v1/instances