diff --git a/tencentcloud/resource_tc_instance.go b/tencentcloud/resource_tc_instance.go index 73414b82d4..4138c45bb8 100644 --- a/tencentcloud/resource_tc_instance.go +++ b/tencentcloud/resource_tc_instance.go @@ -88,7 +88,7 @@ data "tencentcloud_cdh_instances" "list" { } resource "tencentcloud_key_pair" "random_key" { - key_name = "tf_example_key6" + key_ids = ["tf_example_key6"] public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDjd8fTnp7Dcuj4mLaQxf9Zs/ORgUL9fQxRCNKkPgP1paTy1I513maMX126i36Lxxl3+FUB52oVbo/FgwlIfX8hyCnv8MCxqnuSDozf1CD0/wRYHcTWAtgHQHBPCC2nJtod6cVC3kB18KeV4U7zsxmwFeBIxojMOOmcOBuh7+trRw==" } @@ -101,7 +101,7 @@ resource "tencentcloud_instance" "foo" { availability_zone = var.availability_zone instance_name = "terraform-testing" image_id = "img-ix05e4px" - key_name = tencentcloud_key_pair.random_key.id + key_ids = [tencentcloud_key_pair.random_key.id] placement_group_id = tencentcloud_placement_group.foo.id security_groups = ["sg-9c3f33xk"] system_disk_type = "CLOUD_PREMIUM" @@ -424,10 +424,21 @@ func resourceTencentCloudInstance() *schema.Resource { }, // login "key_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - Description: "The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifying will cause the instance reset.", + Type: schema.TypeString, + Optional: true, + Computed: true, + Deprecated: "Please use `key_ids` instead.", + ConflictsWith: []string{"key_ids"}, + Description: "The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifying will cause the instance reset.", + }, + "key_ids": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConflictsWith: []string{"key_name", "password"}, + Description: "The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifying will cause the instance reset.", + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, }, "password": { Type: schema.TypeString, @@ -446,7 +457,7 @@ func resourceTencentCloudInstance() *schema.Resource { return old == new } }, - ConflictsWith: []string{"key_name", "password"}, + ConflictsWith: []string{"key_name", "key_ids", "password"}, Description: "Whether to keep image login or not, default is `false`. When the image type is private or shared or imported, this parameter can be set `true`. Modifying will cause the instance reset.", }, "user_data": { @@ -687,7 +698,10 @@ func resourceTencentCloudInstanceCreate(d *schema.ResourceData, meta interface{} // login request.LoginSettings = &cvm.LoginSettings{} - if v, ok := d.GetOk("key_name"); ok { + keyIds := d.Get("key_ids").(*schema.Set).List() + if len(keyIds) > 0 { + request.LoginSettings.KeyIds = helper.InterfacesStringsPoint(keyIds) + } else if v, ok := d.GetOk("key_name"); ok { request.LoginSettings.KeyIds = []*string{helper.String(v.(string))} } if v, ok := d.GetOk("password"); ok { @@ -753,7 +767,6 @@ func resourceTencentCloudInstanceCreate(d *schema.ResourceData, meta interface{} } d.SetId(instanceId) - // wait for status //get system disk ID and data disk ID var systemDiskId string var dataDiskIds []string @@ -904,7 +917,7 @@ func resourceTencentCloudInstanceRead(d *schema.ResourceData, meta interface{}) return err } - if d.Get("image_id").(string) == "" || !IsContains(cvmImages, *instance.ImageId) { + if d.Get("image_id").(string) == "" || instance.ImageId == nil || !IsContains(cvmImages, *instance.ImageId) { _ = d.Set("image_id", instance.ImageId) } @@ -1005,7 +1018,11 @@ func resourceTencentCloudInstanceRead(d *schema.ResourceData, meta interface{}) _ = d.Set("public_ip", instance.PublicIpAddresses[0]) } if len(instance.LoginSettings.KeyIds) > 0 { - _ = d.Set("key_name", instance.LoginSettings.KeyIds[0]) + if _, ok := d.GetOk("key_name"); ok { + _ = d.Set("key_name", instance.LoginSettings.KeyIds[0]) + } else { + _ = d.Set("key_ids", instance.LoginSettings.KeyIds) + } } else { _ = d.Set("key_name", "") } @@ -1200,7 +1217,10 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{} request.LoginSettings.Password = helper.String(v.(string)) } - if v, ok := d.GetOk("key_name"); ok { + if v, ok := d.GetOk("key_ids"); ok { + updateAttr = append(updateAttr, "key_ids") + request.LoginSettings.KeyIds = helper.InterfacesStringsPoint(v.(*schema.Set).List()) + } else if v, ok := d.GetOk("key_name"); ok { updateAttr = append(updateAttr, "key_name") request.LoginSettings.KeyIds = []*string{helper.String(v.(string))} } @@ -1237,11 +1257,12 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{} } if d.HasChange("key_name") { - old, new := d.GetChange("key_name") - oldKeyId := old.(string) - keyId := new.(string) + o, n := d.GetChange("key_name") + oldKeyId := o.(string) + keyId := n.(string) + if oldKeyId != "" { - err := cvmService.UnbindKeyPair(ctx, oldKeyId, []*string{&instanceId}) + err := cvmService.UnbindKeyPair(ctx, []*string{&oldKeyId}, []*string{&instanceId}) if err != nil { return err } @@ -1252,7 +1273,39 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{} } if keyId != "" { - err = cvmService.BindKeyPair(ctx, keyId, instanceId) + err = cvmService.BindKeyPair(ctx, []*string{&keyId}, instanceId) + if err != nil { + return err + } + err = waitForOperationFinished(d, meta, 2*readRetryTimeout, CVM_LATEST_OPERATION_STATE_OPERATING, false) + if err != nil { + return err + } + } + } + + // support remove old `key_name` to `key_ids`, so do not follow "else" + if d.HasChange("key_ids") { + o, n := d.GetChange("key_ids") + ov := o.(*schema.Set) + + nv := n.(*schema.Set) + + adds := nv.Difference(ov) + removes := ov.Difference(nv) + + if removes.Len() > 0 { + err := cvmService.UnbindKeyPair(ctx, helper.InterfacesStringsPoint(removes.List()), []*string{&instanceId}) + if err != nil { + return err + } + err = waitForOperationFinished(d, meta, 2*readRetryTimeout, CVM_LATEST_OPERATION_STATE_OPERATING, false) + if err != nil { + return err + } + } + if adds.Len() > 0 { + err = cvmService.BindKeyPair(ctx, helper.InterfacesStringsPoint(adds.List()), instanceId) if err != nil { return err } @@ -1261,7 +1314,6 @@ func resourceTencentCloudInstanceUpdate(d *schema.ResourceData, meta interface{} return err } } - d.SetPartial("key_name") } } diff --git a/tencentcloud/resource_tc_instance_test.go b/tencentcloud/resource_tc_instance_test.go index bd6b8ea2dc..6727f2b7f2 100644 --- a/tencentcloud/resource_tc_instance_test.go +++ b/tencentcloud/resource_tc_instance_test.go @@ -206,7 +206,7 @@ func TestAccTencentCloudInstanceWithPrivateIP(t *testing.T) { }) } -func TestAccTencentCloudInstanceWithKeyPair(t *testing.T) { +func TestAccTencentCloudInstanceWithKeyPairs(t *testing.T) { id := "tencentcloud_instance.foo" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -216,12 +216,14 @@ func TestAccTencentCloudInstanceWithKeyPair(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: func() { testAccStepPreConfigSetTempAKSK(t, ACCOUNT_TYPE_COMMON) }, - Config: testAccTencentCloudInstanceWithKeyPair("key_pair_0"), + Config: testAccTencentCloudInstanceWithKeyPair( + "[tencentcloud_key_pair.key_pair_0.id, tencentcloud_key_pair.key_pair_1.id]", + ), Check: resource.ComposeTestCheckFunc( testAccCheckTencentCloudDataSourceID(id), testAccCheckTencentCloudInstanceExists(id), resource.TestCheckResourceAttr(id, "instance_status", "RUNNING"), - resource.TestCheckResourceAttrSet(id, "key_name"), + resource.TestCheckResourceAttr(id, "key_ids.#", "2"), ), }, { @@ -229,12 +231,12 @@ func TestAccTencentCloudInstanceWithKeyPair(t *testing.T) { testAccStepPreConfigSetTempAKSK(t, ACCOUNT_TYPE_COMMON) time.Sleep(time.Duration(time.Second * 5)) }, - Config: testAccTencentCloudInstanceWithKeyPair("key_pair_1"), + Config: testAccTencentCloudInstanceWithKeyPair("[tencentcloud_key_pair.key_pair_2.id]"), Check: resource.ComposeTestCheckFunc( testAccCheckTencentCloudDataSourceID(id), testAccCheckTencentCloudInstanceExists(id), resource.TestCheckResourceAttr(id, "instance_status", "RUNNING"), - resource.TestCheckResourceAttrSet(id, "key_name"), + resource.TestCheckResourceAttr(id, "key_ids.#", "1"), ), }, }, @@ -811,7 +813,8 @@ resource "tencentcloud_instance" "foo" { } ` -func testAccTencentCloudInstanceWithKeyPair(keyName string) string { +func testAccTencentCloudInstanceWithKeyPair(keyIds string) string { + return fmt.Sprintf( defaultInstanceVariable+` resource "tencentcloud_key_pair" "key_pair_0" { @@ -824,16 +827,21 @@ resource "tencentcloud_key_pair" "key_pair_1" { public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCzwYE6KI8uULEvSNA2k1tlsLtMDe+x1Saw6yL3V1mk9NFws0K2BshYqsnP/BlYiGZv/Nld5xmGoA9LupOcUpyyGGSHZdBrMx1Dz9ajewe7kGowRWwwMAHTlzh9+iqeg/v6P5vW6EwK4hpGWgv06vGs3a8CzfbHu1YRbZAO/ysp3ymdL+vGvw/vzC0T+YwPMisn9wFD5FTlJ+Em6s9PzxqR/41t4YssmCwUV78ZoYL8CyB0emuB8wALvcXbdUVxMxpBEHd5U6ZP5+HPxU2WFbWqiFCuErLIZRuxFw8L/Ot+JOyNnadN1XU4crYDX5cML1i/ExXKVIDoBaLtgAJOpyeP" } +resource "tencentcloud_key_pair" "key_pair_2" { + key_name = "key_pair_2" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJ1zyoM55pKxJptZBKceZSEypPN7BOunqBR1Qj3Tz5uImJ+dwfKzggu8PGcbHtuN8D2n1BH/GDkiGFaz/sIYUJWWZudcdut+ra32MqUvk953Sztf12rsFC1+lZ1CYEgon8Lt6ehxn+61tsS31yfUmpL1mq2vuca7J0NLdPMpxIYkGlifyAMISMmxi/m7gPYpbdZTmhQQS2aOhuLm+B4MwtTvT58jqNzIaFU0h5sqAvGQfzI5pcxwYvFTeQeXjJZfaYapDHN0MAg0b/vIWWNrDLv7dlv//OKBIaL0LIzIGQS8XXhF3HlyqfDuf3bjLBIKzYGSV/DRqlEsGBgzinJZXvJZug5oq1n2njDFsdXEvL6fYsP4WLvBLiQlceQ7oXi7m5nfrwFTaX+mpo7dUOR9AcyQ1AAgCcM67orB4E33ycaArGHtpjnCnWUjqQ+yCj4EXsD4yOL77wGsmhkbboVNnYAD9MJWsFP03hZE7p/RHY0C5NfLPT3mL45oZxBpC5mis=" +} + resource "tencentcloud_instance" "foo" { instance_name = var.instance_name availability_zone = var.availability_cvm_zone image_id = data.tencentcloud_images.default.images.0.image_id instance_type = data.tencentcloud_instance_types.default.instance_types.0.instance_type - key_name = tencentcloud_key_pair.%s.id + key_ids = %s system_disk_type = "CLOUD_PREMIUM" } `, - keyName, + keyIds, ) } diff --git a/tencentcloud/resource_tc_key_pair.go b/tencentcloud/resource_tc_key_pair.go index a93127e089..42d9599355 100644 --- a/tencentcloud/resource_tc_key_pair.go +++ b/tencentcloud/resource_tc_key_pair.go @@ -246,7 +246,7 @@ func resourceTencentCloudKeyPairDelete(d *schema.ResourceData, meta interface{}) if len(keyPair.AssociatedInstanceIds) > 0 { err = resource.Retry(writeRetryTimeout, func() *resource.RetryError { - errRet := cvmService.UnbindKeyPair(ctx, keyId, keyPair.AssociatedInstanceIds) + errRet := cvmService.UnbindKeyPair(ctx, []*string{&keyId}, keyPair.AssociatedInstanceIds) if errRet != nil { if sdkErr, ok := errRet.(*errors.TencentCloudSDKError); ok { if sdkErr.Code == CVM_NOT_FOUND_ERROR { diff --git a/tencentcloud/service_tencentcloud_cvm.go b/tencentcloud/service_tencentcloud_cvm.go index 5f2b01bc30..d9fde7f06b 100644 --- a/tencentcloud/service_tencentcloud_cvm.go +++ b/tencentcloud/service_tencentcloud_cvm.go @@ -694,10 +694,10 @@ func (me *CvmService) DeleteKeyPair(ctx context.Context, keyId string) error { return nil } -func (me *CvmService) UnbindKeyPair(ctx context.Context, keyId string, instanceIds []*string) error { +func (me *CvmService) UnbindKeyPair(ctx context.Context, keyIds []*string, instanceIds []*string) error { logId := getLogId(ctx) request := cvm.NewDisassociateInstancesKeyPairsRequest() - request.KeyIds = []*string{&keyId} + request.KeyIds = keyIds request.InstanceIds = instanceIds request.ForceStop = helper.Bool(true) @@ -714,10 +714,10 @@ func (me *CvmService) UnbindKeyPair(ctx context.Context, keyId string, instanceI return nil } -func (me *CvmService) BindKeyPair(ctx context.Context, keyId, instanceId string) error { +func (me *CvmService) BindKeyPair(ctx context.Context, keyIds []*string, instanceId string) error { logId := getLogId(ctx) request := cvm.NewAssociateInstancesKeyPairsRequest() - request.KeyIds = []*string{&keyId} + request.KeyIds = keyIds request.InstanceIds = []*string{&instanceId} request.ForceStop = helper.Bool(true) diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index fbebf6f8d7..db2cfefebb 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -99,7 +99,7 @@ data "tencentcloud_cdh_instances" "list" { } resource "tencentcloud_key_pair" "random_key" { - key_name = "tf_example_key6" + key_ids = ["tf_example_key6"] public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDjd8fTnp7Dcuj4mLaQxf9Zs/ORgUL9fQxRCNKkPgP1paTy1I513maMX126i36Lxxl3+FUB52oVbo/FgwlIfX8hyCnv8MCxqnuSDozf1CD0/wRYHcTWAtgHQHBPCC2nJtod6cVC3kB18KeV4U7zsxmwFeBIxojMOOmcOBuh7+trRw==" } @@ -112,7 +112,7 @@ resource "tencentcloud_instance" "foo" { availability_zone = var.availability_zone instance_name = "terraform-testing" image_id = "img-ix05e4px" - key_name = tencentcloud_key_pair.random_key.id + key_ids = [tencentcloud_key_pair.random_key.id] placement_group_id = tencentcloud_placement_group.foo.id security_groups = ["sg-9c3f33xk"] system_disk_type = "CLOUD_PREMIUM" @@ -160,7 +160,8 @@ The following arguments are supported: * `internet_charge_type` - (Optional, String) Internet charge type of the instance, Valid values are `BANDWIDTH_PREPAID`, `TRAFFIC_POSTPAID_BY_HOUR`, `BANDWIDTH_POSTPAID_BY_HOUR` and `BANDWIDTH_PACKAGE`. This value takes NO Effect when changing and does not need to be set when `allocate_public_ip` is false. * `internet_max_bandwidth_out` - (Optional, Int) Maximum outgoing bandwidth to the public network, measured in Mbps (Mega bits per second). This value does not need to be set when `allocate_public_ip` is false. * `keep_image_login` - (Optional, Bool) Whether to keep image login or not, default is `false`. When the image type is private or shared or imported, this parameter can be set `true`. Modifying will cause the instance reset. -* `key_name` - (Optional, String) The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifying will cause the instance reset. +* `key_ids` - (Optional, Set: [`String`]) The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifying will cause the instance reset. +* `key_name` - (Optional, String, **Deprecated**) Please use `key_ids` instead. The key pair to use for the instance, it looks like `skey-16jig7tx`. Modifying will cause the instance reset. * `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. Modifying will cause the instance reset. * `placement_group_id` - (Optional, String, ForceNew) 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.