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.
This commit is contained in:
ZhiQiang Fan 2019-06-23 23:23:48 +08:00
parent 757bd4bed9
commit 1c7b23357d
7 changed files with 103 additions and 13 deletions

View File

@ -115,9 +115,15 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}, },
&common.StepProvision{}, &common.StepProvision{},
&common.StepCleanupTempKeys{ &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{}, &stepCreateImage{},
&stepShareImage{b.config.ImageShareAccounts}, &stepShareImage{
b.config.ImageShareAccounts,
},
&stepCopyImage{ &stepCopyImage{
DesinationRegions: b.config.ImageCopyRegions, DesinationRegions: b.config.ImageCopyRegions,
SourceRegion: b.config.Region, SourceRegion: b.config.Region,

View File

@ -70,6 +70,7 @@ func (s *stepConfigKeyPair) Run(ctx context.Context, state multistep.StateBag) m
// set keyId to delete when Cleanup // set keyId to delete when Cleanup
s.keyID = *resp.Response.KeyPair.KeyId 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.SSHKeyPairName = *resp.Response.KeyPair.KeyId
s.Comm.SSHPrivateKey = []byte(*resp.Response.KeyPair.PrivateKey) s.Comm.SSHPrivateKey = []byte(*resp.Response.KeyPair.PrivateKey)

View File

@ -106,7 +106,7 @@ func (s *stepConfigSecurityGroup) Cleanup(state multistep.StateBag) {
vpcClient := state.Get("vpc_client").(*vpc.Client) vpcClient := state.Get("vpc_client").(*vpc.Client)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
MessageClean(state, "VPC") MessageClean(state, "Security Group")
req := vpc.NewDeleteSecurityGroupRequest() req := vpc.NewDeleteSecurityGroupRequest()
req.SecurityGroupId = &s.SecurityGroupId req.SecurityGroupId = &s.SecurityGroupId
err := retry.Config{ err := retry.Config{

View File

@ -3,9 +3,12 @@ package cvm
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" 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 req.Sysprep = &False
} }
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) _, err := client.CreateImage(req)
return err
})
if err != nil { if err != nil {
err := fmt.Errorf("create image failed: %s", err.Error()) err := fmt.Errorf("create image failed: %s", err.Error())
state.Put("error", err) state.Put("error", err)

View File

@ -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) {
}

View File

@ -8,6 +8,7 @@ import (
"log" "log"
"time" "time"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
@ -143,15 +144,23 @@ func (s *stepRunInstance) Cleanup(state multistep.StateBag) {
if s.instanceId == "" { if s.instanceId == "" {
return return
} }
MessageClean(state, "instance") MessageClean(state, "Instance")
client := state.Get("cvm_client").(*cvm.Client) client := state.Get("cvm_client").(*cvm.Client)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
req := cvm.NewTerminateInstancesRequest() req := cvm.NewTerminateInstancesRequest()
req.InstanceIds = []*string{&s.instanceId} req.InstanceIds = []*string{&s.instanceId}
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) _, err := client.TerminateInstances(req)
// The binding relation between instance and vpc would last few minutes after return err
// instance terminate, we sleep here to give more time })
time.Sleep(2 * time.Minute)
if err != nil { if err != nil {
ui.Error(fmt.Sprintf("terminate instance(%s) failed: %s", s.instanceId, err.Error())) ui.Error(fmt.Sprintf("terminate instance(%s) failed: %s", s.instanceId, err.Error()))
} }

View File

@ -8,11 +8,12 @@
"secret_id": "{{user `secret_id`}}", "secret_id": "{{user `secret_id`}}",
"secret_key": "{{user `secret_key`}}", "secret_key": "{{user `secret_key`}}",
"region": "ap-guangzhou", "region": "ap-guangzhou",
"zone": "ap-guangzhou-3", "zone": "ap-guangzhou-4",
"instance_type": "S3.SMALL1", "instance_type": "S4.SMALL1",
"source_image_id": "img-oikl1tzv", "source_image_id": "img-oikl1tzv",
"ssh_username" : "root", "ssh_username" : "root",
"image_name": "packerTest2", "image_name": "PackerTest",
"disk_type": "CLOUD_PREMIUM",
"packer_debug": true, "packer_debug": true,
"associate_public_ip_address": true "associate_public_ip_address": true
}], }],