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 +}