package cvm import ( "context" "fmt" "regexp" "strings" "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" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312" ) // DefaultWaitForInterval is sleep interval when wait statue const DefaultWaitForInterval = 5 // WaitForInstance wait for instance reaches statue func WaitForInstance(ctx context.Context, client *cvm.Client, instanceId string, status string, timeout int) error { req := cvm.NewDescribeInstancesRequest() req.InstanceIds = []*string{&instanceId} for { var resp *cvm.DescribeInstancesResponse err := Retry(ctx, func(ctx context.Context) error { var e error resp, e = client.DescribeInstances(req) return e }) if err != nil { return err } if *resp.Response.TotalCount == 0 { return fmt.Errorf("instance(%s) not exist", instanceId) } if *resp.Response.InstanceSet[0].InstanceState == status { break } time.Sleep(DefaultWaitForInterval * time.Second) timeout = timeout - DefaultWaitForInterval if timeout <= 0 { return fmt.Errorf("wait instance(%s) status(%s) timeout", instanceId, status) } } return nil } // WaitForImageReady wait for image reaches statue func WaitForImageReady(ctx context.Context, client *cvm.Client, imageName string, status string, timeout int) error { for { image, err := GetImageByName(ctx, client, imageName) if err != nil { return err } if image != nil && *image.ImageState == status { return nil } time.Sleep(DefaultWaitForInterval * time.Second) timeout = timeout - DefaultWaitForInterval if timeout <= 0 { return fmt.Errorf("wait image(%s) status(%s) timeout", imageName, status) } } } // GetImageByName get image by image name func GetImageByName(ctx context.Context, client *cvm.Client, imageName string) (*cvm.Image, error) { req := cvm.NewDescribeImagesRequest() req.Filters = []*cvm.Filter{ { Name: common.StringPtr("image-name"), Values: []*string{&imageName}, }, } var resp *cvm.DescribeImagesResponse err := Retry(ctx, func(ctx context.Context) error { var e error resp, e = client.DescribeImages(req) return e }) if err != nil { return nil, err } if *resp.Response.TotalCount > 0 { for _, image := range resp.Response.ImageSet { if *image.ImageName == imageName { return image, nil } } } return nil, nil } // NewCvmClient returns a new cvm client func NewCvmClient(secretId, secretKey, region string) (client *cvm.Client, err error) { cpf := profile.NewClientProfile() cpf.HttpProfile.ReqMethod = "POST" cpf.HttpProfile.ReqTimeout = 300 cpf.Language = "en-US" credential := common.NewCredential(secretId, secretKey) client, err = cvm.NewClient(credential, region, cpf) return } // NewVpcClient returns a new vpc client func NewVpcClient(secretId, secretKey, region string) (client *vpc.Client, err error) { cpf := profile.NewClientProfile() cpf.HttpProfile.ReqMethod = "POST" cpf.HttpProfile.ReqTimeout = 300 cpf.Language = "en-US" credential := common.NewCredential(secretId, secretKey) client, err = vpc.NewClient(credential, region, cpf) return } // CheckResourceIdFormat check resource id format func CheckResourceIdFormat(resource string, id string) bool { regex := regexp.MustCompile(fmt.Sprintf("%s-[0-9a-z]{8}$", resource)) return regex.MatchString(id) } // SSHHost returns a function that can be given to the SSH communicator func SSHHost(pubilcIp bool) func(multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { instance := state.Get("instance").(*cvm.Instance) if pubilcIp { return *instance.PublicIpAddresses[0], nil } else { return *instance.PrivateIpAddresses[0], nil } } } // Retry do retry on api request func Retry(ctx context.Context, fn func(context.Context) error) error { return retry.Config{ Tries: 30, ShouldRetry: func(err error) bool { e, ok := err.(*errors.TencentCloudSDKError) if !ok { return false } if e.Code == "ClientError.NetworkError" || e.Code == "ClientError.HttpStatusCodeError" || e.Code == "InvalidKeyPair.NotSupported" || e.Code == "InvalidInstance.NotSupported" || strings.Contains(e.Code, "RequestLimitExceeded") || strings.Contains(e.Code, "InternalError") || strings.Contains(e.Code, "ResourceInUse") || strings.Contains(e.Code, "ResourceBusy") { return true } return false }, RetryDelay: (&retry.Backoff{ InitialBackoff: 1 * time.Second, MaxBackoff: 5 * time.Second, Multiplier: 2, }).Linear, }.Run(ctx, fn) } // SayClean tell you clean module message func SayClean(state multistep.StateBag, module string) { _, halted := state.GetOk(multistep.StateHalted) _, cancelled := state.GetOk(multistep.StateCancelled) if halted { Say(state, fmt.Sprintf("Deleting %s because of error...", module), "") } else if cancelled { Say(state, fmt.Sprintf("Deleting %s because of cancellation...", module), "") } else { Say(state, fmt.Sprintf("Cleaning up %s...", module), "") } } // Say tell you a message func Say(state multistep.StateBag, message, prefix string) { if prefix != "" { message = fmt.Sprintf("%s: %s", prefix, message) } if strings.HasPrefix(message, "Trying to") { message += "..." } ui := state.Get("ui").(packer.Ui) ui.Say(message) } // Message print a message func Message(state multistep.StateBag, message, prefix string) { if prefix != "" { message = fmt.Sprintf("%s: %s", prefix, message) } ui := state.Get("ui").(packer.Ui) ui.Message(message) } // Error print error message func Error(state multistep.StateBag, err error, prefix string) { if prefix != "" { err = fmt.Errorf("%s: %s", prefix, err) } ui := state.Get("ui").(packer.Ui) ui.Error(err.Error()) } // Halt print error message and exit func Halt(state multistep.StateBag, err error, prefix string) multistep.StepAction { Error(state, err, prefix) state.Put("error", err) return multistep.ActionHalt }