diff --git a/.changelog/3383.txt b/.changelog/3383.txt new file mode 100644 index 0000000000..144fc51f2f --- /dev/null +++ b/.changelog/3383.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/tencentcloud_instance: support modify `placement_group_id` +``` \ No newline at end of file diff --git a/tencentcloud/services/cvm/resource_tc_instance.go b/tencentcloud/services/cvm/resource_tc_instance.go index 975079112f..ce7c5d3723 100644 --- a/tencentcloud/services/cvm/resource_tc_instance.go +++ b/tencentcloud/services/cvm/resource_tc_instance.go @@ -108,9 +108,14 @@ func ResourceTencentCloudInstance() *schema.Resource { "placement_group_id": { Type: schema.TypeString, Optional: true, - ForceNew: true, Description: "The ID of a placement group.", }, + "force_replace_placement_group_id": { + Type: schema.TypeBool, + Optional: true, + RequiredWith: []string{"placement_group_id"}, + Description: "Whether to force the instance host to be replaced. Value range: true: Allows the instance to change the host and restart the instance. Local disk machines do not support specifying this parameter; false: Does not allow the instance to change the host and only join the placement group on the current host. This may cause the placement group to fail to change. Only useful for change `placement_group_id`, Default is false.", + }, // payment "instance_charge_type": { Type: schema.TypeString, @@ -576,8 +581,18 @@ func resourceTencentCloudInstanceCreate(d *schema.ResourceData, meta interface{} } } - if v, ok := d.GetOk("placement_group_id"); ok { - request.DisasterRecoverGroupIds = []*string{helper.String(v.(string))} + var ( + rpgFlag bool + ) + + if v, ok := d.GetOkExists("force_replace_placement_group_id"); ok { + rpgFlag = v.(bool) + } + + if !rpgFlag { + if v, ok := d.GetOk("placement_group_id"); ok { + request.DisasterRecoverGroupIds = []*string{helper.String(v.(string))} + } } // network @@ -835,6 +850,57 @@ func resourceTencentCloudInstanceCreate(d *schema.ResourceData, meta interface{} return err } + // set placement group id + if rpgFlag { + if v, ok := d.GetOk("placement_group_id"); ok && v != "" { + request := cvm.NewModifyInstancesDisasterRecoverGroupRequest() + request.InstanceIds = helper.Strings([]string{instanceId}) + request.DisasterRecoverGroupId = helper.String(v.(string)) + request.Force = helper.Bool(rpgFlag) + err = resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseCvmClient().ModifyInstancesDisasterRecoverGroup(request) + if e != nil { + return tccommon.RetryError(e) + } else { + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + } + + return nil + }) + + if err != nil { + return err + } + + // wait + err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId) + if errRet != nil { + return tccommon.RetryError(errRet, tccommon.InternalError) + } + + if instance != nil && *instance.InstanceState == CVM_STATUS_LAUNCH_FAILED { + //LatestOperationCodeMode + if instance.LatestOperationErrorMsg != nil { + return resource.NonRetryableError(fmt.Errorf("cvm instance %s launch failed. Error msg: %s.\n", *instance.InstanceId, *instance.LatestOperationErrorMsg)) + } + + return resource.NonRetryableError(fmt.Errorf("cvm instance %s launch failed, this resource will not be stored to tfstate and will auto removed\n.", *instance.InstanceId)) + } + + if instance != nil && *instance.InstanceState == CVM_STATUS_RUNNING { + return nil + } + + return resource.RetryableError(fmt.Errorf("cvm instance status is %s, retry...", *instance.InstanceState)) + }) + + if err != nil { + return err + } + } + } + // Wait for the tags attached to the vm since tags attachment it's async while vm creation. if tags := helper.GetTags(d, "tags"); len(tags) > 0 { tcClient := meta.(tccommon.ProviderMeta).GetAPIV3Conn() @@ -988,6 +1054,10 @@ func resourceTencentCloudInstanceRead(d *schema.ResourceData, meta interface{}) _ = d.Set("uuid", instance.Uuid) } + if instance.DisasterRecoverGroupId != nil { + _ = d.Set("placement_group_id", instance.DisasterRecoverGroupId) + } + if *instance.InstanceChargeType == CVM_CHARGE_TYPE_CDHPAID { _ = d.Set("cdh_instance_type", instance.InstanceType) } @@ -2027,6 +2097,7 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{} return err } } + if d.HasChange("user_data_raw") { userDataRaw := d.Get("user_data_raw").(string) userData := base64.StdEncoding.EncodeToString([]byte(userDataRaw)) @@ -2040,6 +2111,70 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{} return err } } + + if d.HasChange("placement_group_id") || d.HasChange("force_replace_placement_group_id") { + oldPGI, newPGI := d.GetChange("placement_group_id") + oldPGIStr := oldPGI.(string) + newPGIStr := newPGI.(string) + if newPGIStr == "" { + // wait cvm support delete DisasterRecoverGroupId + return fmt.Errorf("Deleting `placement_group_id` is not currently supported.") + } else { + if oldPGIStr == newPGIStr { + return fmt.Errorf("It is not possible to change only `force_replace_placement_group_id`, it needs to be modified together with `placement_group_id`.") + } + + request := cvm.NewModifyInstancesDisasterRecoverGroupRequest() + if v, ok := d.GetOkExists("force_replace_placement_group_id"); ok { + request.Force = helper.Bool(v.(bool)) + } + + request.InstanceIds = helper.Strings([]string{instanceId}) + request.DisasterRecoverGroupId = helper.String(newPGIStr) + err = resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseCvmClient().ModifyInstancesDisasterRecoverGroup(request) + if e != nil { + return tccommon.RetryError(e) + } else { + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + } + + return nil + }) + + if err != nil { + return err + } + + // wait + err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + instance, errRet := cvmService.DescribeInstanceById(ctx, instanceId) + if errRet != nil { + return tccommon.RetryError(errRet, tccommon.InternalError) + } + + if instance != nil && *instance.InstanceState == CVM_STATUS_LAUNCH_FAILED { + //LatestOperationCodeMode + if instance.LatestOperationErrorMsg != nil { + return resource.NonRetryableError(fmt.Errorf("cvm instance %s launch failed. Error msg: %s.\n", *instance.InstanceId, *instance.LatestOperationErrorMsg)) + } + + return resource.NonRetryableError(fmt.Errorf("cvm instance %s launch failed, this resource will not be stored to tfstate and will auto removed\n.", *instance.InstanceId)) + } + + if instance != nil && *instance.InstanceState == CVM_STATUS_RUNNING { + return nil + } + + return resource.RetryableError(fmt.Errorf("cvm instance status is %s, retry...", *instance.InstanceState)) + }) + + if err != nil { + return err + } + } + } + d.Partial(false) return resourceTencentCloudInstanceRead(d, meta) diff --git a/tencentcloud/services/cvm/resource_tc_instance.md b/tencentcloud/services/cvm/resource_tc_instance.md index 6fd74b8419..ecc96d1035 100644 --- a/tencentcloud/services/cvm/resource_tc_instance.md +++ b/tencentcloud/services/cvm/resource_tc_instance.md @@ -2,7 +2,9 @@ Provides a CVM instance resource. ~> **NOTE:** You can launch an CVM instance for a VPC network via specifying parameter `vpc_id`. One instance can only belong to one VPC. -~> **NOTE:** At present, 'PREPAID' instance cannot be deleted directly and must wait it to be outdated and released automatically. +~> **NOTE:** At present, `PREPAID` instance cannot be deleted directly and must wait it to be outdated and released automatically. + +~> **NOTE:** Currently, the `placement_group_id` field only supports setting and modification, but not deletion. Example Usage @@ -200,6 +202,36 @@ resource "tencentcloud_instance" "example" { } ``` +Create CVM instance with placement_group_id + +```hcl +resource "tencentcloud_instance" "example" { + instance_name = "tf-example" + availability_zone = "ap-guangzhou-6" + image_id = "img-eb30mz89" + instance_type = "S5.MEDIUM4" + system_disk_size = 50 + system_disk_name = "sys_disk_1" + hostname = "user" + project_id = 0 + vpc_id = "vpc-i5yyodl9" + subnet_id = "subnet-hhi88a58" + placement_group_id = "ps-ejt4brtz" + force_replace_placement_group_id = false + + data_disks { + data_disk_type = "CLOUD_HSSD" + data_disk_size = 100 + encrypt = false + data_disk_name = "data_disk_1" + } + + tags = { + tagKey = "tagValue" + } +} +``` + Import CVM instance can be imported using the id, e.g. diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index 7fd10a3dda..c4c5408217 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -13,7 +13,9 @@ Provides a CVM instance resource. ~> **NOTE:** You can launch an CVM instance for a VPC network via specifying parameter `vpc_id`. One instance can only belong to one VPC. -~> **NOTE:** At present, 'PREPAID' instance cannot be deleted directly and must wait it to be outdated and released automatically. +~> **NOTE:** At present, `PREPAID` instance cannot be deleted directly and must wait it to be outdated and released automatically. + +~> **NOTE:** Currently, the `placement_group_id` field only supports setting and modification, but not deletion. ## Example Usage @@ -211,6 +213,36 @@ resource "tencentcloud_instance" "example" { } ``` +### Create CVM instance with placement_group_id + +```hcl +resource "tencentcloud_instance" "example" { + instance_name = "tf-example" + availability_zone = "ap-guangzhou-6" + image_id = "img-eb30mz89" + instance_type = "S5.MEDIUM4" + system_disk_size = 50 + system_disk_name = "sys_disk_1" + hostname = "user" + project_id = 0 + vpc_id = "vpc-i5yyodl9" + subnet_id = "subnet-hhi88a58" + placement_group_id = "ps-ejt4brtz" + force_replace_placement_group_id = false + + data_disks { + data_disk_type = "CLOUD_HSSD" + data_disk_size = 100 + encrypt = false + data_disk_name = "data_disk_1" + } + + tags = { + tagKey = "tagValue" + } +} +``` + ## Argument Reference The following arguments are supported: @@ -229,6 +261,7 @@ The following arguments are supported: * `disable_monitor_service` - (Optional, Bool) Disable enhance service for monitor, it is enabled by default. When this options is set, monitor agent won't be installed. Modifications may lead to the reinstallation of the instance's operating system. * `disable_security_service` - (Optional, Bool) Disable enhance service for security, it is enabled by default. When this options is set, security agent won't be installed. Modifications may lead to the reinstallation of the instance's operating system. * `force_delete` - (Optional, Bool) Indicate whether to force delete the instance. Default is `false`. If set true, the instance will be permanently deleted instead of being moved into the recycle bin. Note: only works for `PREPAID` instance. +* `force_replace_placement_group_id` - (Optional, Bool) Whether to force the instance host to be replaced. Value range: true: Allows the instance to change the host and restart the instance. Local disk machines do not support specifying this parameter; false: Does not allow the instance to change the host and only join the placement group on the current host. This may cause the placement group to fail to change. Only useful for change `placement_group_id`, Default is false. * `hostname` - (Optional, String) The hostname of the instance. Windows instance: The name should be a combination of 2 to 15 characters comprised of letters (case insensitive), numbers, and hyphens (-). Period (.) is not supported, and the name cannot be a string of pure numbers. Other types (such as Linux) of instances: The name should be a combination of 2 to 60 characters, supporting multiple periods (.). The piece between two periods is composed of letters (case insensitive), numbers, and hyphens (-). Modifications may lead to the reinstallation of the instance's operating system. * `hpc_cluster_id` - (Optional, String, ForceNew) High-performance computing cluster ID. If the instance created is a high-performance computing instance, you need to specify the cluster in which the instance is placed, otherwise it cannot be specified. * `instance_charge_type_prepaid_period` - (Optional, Int) The tenancy (time unit is month) of the prepaid instance, NOTE: it only works when instance_charge_type is set to `PREPAID`. Valid values are `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12`, `24`, `36`, `48`, `60`. @@ -244,7 +277,7 @@ The following arguments are supported: * `key_name` - (Optional, String, **Deprecated**) Please use `key_ids` instead. The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifications may lead to the reinstallation of the instance's operating system. * `orderly_security_groups` - (Optional, List: [`String`]) A list of orderly security group IDs to associate with. * `password` - (Optional, String) Password for the instance. In order for the new password to take effect, the instance will be restarted after the password change. Modifications may lead to the reinstallation of the instance's operating system. -* `placement_group_id` - (Optional, String, ForceNew) The ID of a placement group. +* `placement_group_id` - (Optional, String) The ID of a placement group. * `private_ip` - (Optional, String) The private IP to be assigned to this instance, must be in the provided subnet and available. * `project_id` - (Optional, Int) The project the instance belongs to, default to 0. * `running_flag` - (Optional, Bool) Set instance to running or stop. Default value is true, the instance will shutdown when this flag is false.