From 1c7b23357dcc092b888d081f2bbbd2d93821bbe5 Mon Sep 17 00:00:00 2001 From: ZhiQiang Fan Date: Sun, 23 Jun 2019 23:23:48 +0800 Subject: [PATCH] fix: correctly remove tencentcloud temporary keypair Tencent Cloud key pair cannot be removed if it is in a image, so when user doesn't specify any log in method, such as ssh_password, temporary key pair will be created and used, which eventually will always fail. This patch detach temporary key pair before creating image, so in cleanup step, it can be deleted correctly. Note that if user specifies a private key pair, we do not detach it, because user might want to use it when creating new instances from this image. --- builder/tencentcloud/cvm/builder.go | 10 +++- .../tencentcloud/cvm/step_config_key_pair.go | 1 + .../cvm/step_config_security_group.go | 2 +- builder/tencentcloud/cvm/step_create_image.go | 24 ++++++++- .../cvm/step_detach_temp_key_pair.go | 51 +++++++++++++++++++ builder/tencentcloud/cvm/step_run_instance.go | 19 +++++-- examples/tencentcloud/basic.json | 9 ++-- 7 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 builder/tencentcloud/cvm/step_detach_temp_key_pair.go diff --git a/builder/tencentcloud/cvm/builder.go b/builder/tencentcloud/cvm/builder.go index a0a12f348..d73428665 100644 --- a/builder/tencentcloud/cvm/builder.go +++ b/builder/tencentcloud/cvm/builder.go @@ -115,9 +115,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack }, &common.StepProvision{}, &common.StepCleanupTempKeys{ - Comm: &b.config.TencentCloudRunConfig.Comm}, + Comm: &b.config.TencentCloudRunConfig.Comm, + }, + // We need this step to detach temporary key from instance, otherwise + // it always fails to delete the key. + &stepDetachTempKeyPair{}, &stepCreateImage{}, - &stepShareImage{b.config.ImageShareAccounts}, + &stepShareImage{ + b.config.ImageShareAccounts, + }, &stepCopyImage{ DesinationRegions: b.config.ImageCopyRegions, SourceRegion: b.config.Region, diff --git a/builder/tencentcloud/cvm/step_config_key_pair.go b/builder/tencentcloud/cvm/step_config_key_pair.go index a1768f67d..dfaa97a0d 100644 --- a/builder/tencentcloud/cvm/step_config_key_pair.go +++ b/builder/tencentcloud/cvm/step_config_key_pair.go @@ -70,6 +70,7 @@ func (s *stepConfigKeyPair) Run(ctx context.Context, state multistep.StateBag) m // set keyId to delete when Cleanup s.keyID = *resp.Response.KeyPair.KeyId + state.Put("temporary_key_pair_id", resp.Response.KeyPair.KeyId) s.Comm.SSHKeyPairName = *resp.Response.KeyPair.KeyId s.Comm.SSHPrivateKey = []byte(*resp.Response.KeyPair.PrivateKey) diff --git a/builder/tencentcloud/cvm/step_config_security_group.go b/builder/tencentcloud/cvm/step_config_security_group.go index 9244f4cc6..2b107615a 100644 --- a/builder/tencentcloud/cvm/step_config_security_group.go +++ b/builder/tencentcloud/cvm/step_config_security_group.go @@ -106,7 +106,7 @@ func (s *stepConfigSecurityGroup) Cleanup(state multistep.StateBag) { vpcClient := state.Get("vpc_client").(*vpc.Client) ui := state.Get("ui").(packer.Ui) - MessageClean(state, "VPC") + MessageClean(state, "Security Group") req := vpc.NewDeleteSecurityGroupRequest() req.SecurityGroupId = &s.SecurityGroupId err := retry.Config{ diff --git a/builder/tencentcloud/cvm/step_create_image.go b/builder/tencentcloud/cvm/step_create_image.go index f4cfa9d4f..85defb37d 100644 --- a/builder/tencentcloud/cvm/step_create_image.go +++ b/builder/tencentcloud/cvm/step_create_image.go @@ -3,9 +3,12 @@ package cvm import ( "context" "fmt" + "time" + "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" ) @@ -46,7 +49,26 @@ func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul req.Sysprep = &False } - _, err := client.CreateImage(req) + err := retry.Config{ + Tries: 60, + RetryDelay: (&retry.Backoff{ + InitialBackoff: 5 * time.Second, + MaxBackoff: 5 * time.Second, + Multiplier: 2, + }).Linear, + ShouldRetry: func(err error) bool { + if e, ok := err.(*errors.TencentCloudSDKError); ok { + if e.Code == "InvalidImageName.Duplicate" { + return false + } + } + return true + }, + }.Run(ctx, func(ctx context.Context) error { + _, err := client.CreateImage(req) + return err + }) + if err != nil { err := fmt.Errorf("create image failed: %s", err.Error()) state.Put("error", err) diff --git a/builder/tencentcloud/cvm/step_detach_temp_key_pair.go b/builder/tencentcloud/cvm/step_detach_temp_key_pair.go new file mode 100644 index 000000000..ea1feff73 --- /dev/null +++ b/builder/tencentcloud/cvm/step_detach_temp_key_pair.go @@ -0,0 +1,51 @@ +package cvm + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/packer/common/retry" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" +) + +type stepDetachTempKeyPair struct { +} + +func (s *stepDetachTempKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + client := state.Get("cvm_client").(*cvm.Client) + instance := state.Get("instance").(*cvm.Instance) + if _, ok := state.GetOk("temporary_key_pair_id"); !ok { + return multistep.ActionContinue + } + keyId := state.Get("temporary_key_pair_id").(*string) + ui := state.Get("ui").(packer.Ui) + ui.Say(fmt.Sprintf("Detaching temporary key pair %s...", *keyId)) + req := cvm.NewDisassociateInstancesKeyPairsRequest() + req.KeyIds = []*string{keyId} + req.InstanceIds = []*string{instance.InstanceId} + req.ForceStop = common.BoolPtr(true) + err := retry.Config{ + Tries: 60, + RetryDelay: (&retry.Backoff{ + InitialBackoff: 5 * time.Second, + MaxBackoff: 5 * time.Second, + Multiplier: 2, + }).Linear, + }.Run(ctx, func(ctx context.Context) error { + _, err := client.DisassociateInstancesKeyPairs(req) + return err + }) + if err != nil { + ui.Error(fmt.Sprintf("Fail to detach temporary key pair from instance! Error: %s", err)) + state.Put("error", err) + return multistep.ActionHalt + } + return multistep.ActionContinue +} + +func (s *stepDetachTempKeyPair) Cleanup(state multistep.StateBag) { +} diff --git a/builder/tencentcloud/cvm/step_run_instance.go b/builder/tencentcloud/cvm/step_run_instance.go index fab521e6d..a8c6c072d 100644 --- a/builder/tencentcloud/cvm/step_run_instance.go +++ b/builder/tencentcloud/cvm/step_run_instance.go @@ -8,6 +8,7 @@ import ( "log" "time" + "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" @@ -143,15 +144,23 @@ func (s *stepRunInstance) Cleanup(state multistep.StateBag) { if s.instanceId == "" { return } - MessageClean(state, "instance") + MessageClean(state, "Instance") client := state.Get("cvm_client").(*cvm.Client) ui := state.Get("ui").(packer.Ui) req := cvm.NewTerminateInstancesRequest() req.InstanceIds = []*string{&s.instanceId} - _, err := client.TerminateInstances(req) - // The binding relation between instance and vpc would last few minutes after - // instance terminate, we sleep here to give more time - time.Sleep(2 * time.Minute) + ctx := context.TODO() + err := retry.Config{ + Tries: 60, + RetryDelay: (&retry.Backoff{ + InitialBackoff: 5 * time.Second, + MaxBackoff: 5 * time.Second, + Multiplier: 2, + }).Linear, + }.Run(ctx, func(ctx context.Context) error { + _, err := client.TerminateInstances(req) + return err + }) if err != nil { ui.Error(fmt.Sprintf("terminate instance(%s) failed: %s", s.instanceId, err.Error())) } diff --git a/examples/tencentcloud/basic.json b/examples/tencentcloud/basic.json index 62423d9ab..5229a4c73 100644 --- a/examples/tencentcloud/basic.json +++ b/examples/tencentcloud/basic.json @@ -8,11 +8,12 @@ "secret_id": "{{user `secret_id`}}", "secret_key": "{{user `secret_key`}}", "region": "ap-guangzhou", - "zone": "ap-guangzhou-3", - "instance_type": "S3.SMALL1", + "zone": "ap-guangzhou-4", + "instance_type": "S4.SMALL1", "source_image_id": "img-oikl1tzv", "ssh_username" : "root", - "image_name": "packerTest2", + "image_name": "PackerTest", + "disk_type": "CLOUD_PREMIUM", "packer_debug": true, "associate_public_ip_address": true }], @@ -23,4 +24,4 @@ "yum install redis.x86_64 -y" ] }] -} \ No newline at end of file +}