package cvm import ( "context" "encoding/base64" "fmt" "io/ioutil" "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" ) type stepRunInstance struct { InstanceType string UserData string UserDataFile string instanceId string ZoneId string InstanceName string DiskType string DiskSize int64 HostName string InternetMaxBandwidthOut int64 AssociatePublicIpAddress bool Tags map[string]string DataDisks []tencentCloudDataDisk } func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { client := state.Get("cvm_client").(*cvm.Client) config := state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui) source_image := state.Get("source_image").(*cvm.Image) vpc_id := state.Get("vpc_id").(string) subnet_id := state.Get("subnet_id").(string) security_group_id := state.Get("security_group_id").(string) password := config.Comm.SSHPassword if password == "" && config.Comm.WinRMPassword != "" { password = config.Comm.WinRMPassword } userData, err := s.getUserData(state) if err != nil { err := fmt.Errorf("get user_data failed: %s", err.Error()) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } ui.Say("Creating Instance.") // config RunInstances parameters POSTPAID_BY_HOUR := "POSTPAID_BY_HOUR" req := cvm.NewRunInstancesRequest() if s.ZoneId != "" { req.Placement = &cvm.Placement{ Zone: &s.ZoneId, } } req.ImageId = source_image.ImageId req.InstanceChargeType = &POSTPAID_BY_HOUR req.InstanceType = &s.InstanceType // TODO: Add check for system disk size, it should be larger than image system disk size. req.SystemDisk = &cvm.SystemDisk{ DiskType: &s.DiskType, DiskSize: &s.DiskSize, } // System disk snapshot is mandatory, so if there are additional data disks, // length will be larger than 1. if source_image.SnapshotSet != nil && len(source_image.SnapshotSet) > 1 { ui.Say("Use source image snapshot data disks, ignore user data disk settings.") var dataDisks []*cvm.DataDisk for _, snapshot := range source_image.SnapshotSet { if *snapshot.DiskUsage == "DATA_DISK" { var dataDisk cvm.DataDisk // FIXME: Currently we have no way to get original disk type // from data disk snapshots, and we don't allow user to overwrite // snapshot settings, and we cannot guarantee a certain hard-coded type // is not sold out, so here we use system disk type as a workaround. // // Eventually, we need to allow user to overwrite snapshot disk // settings. dataDisk.DiskType = &s.DiskType dataDisk.DiskSize = snapshot.DiskSize dataDisk.SnapshotId = snapshot.SnapshotId dataDisks = append(dataDisks, &dataDisk) } } req.DataDisks = dataDisks } else { var dataDisks []*cvm.DataDisk for _, disk := range s.DataDisks { var dataDisk cvm.DataDisk dataDisk.DiskType = &disk.DiskType dataDisk.DiskSize = &disk.DiskSize if disk.SnapshotId != "" { dataDisk.SnapshotId = &disk.SnapshotId } dataDisks = append(dataDisks, &dataDisk) } req.DataDisks = dataDisks } req.VirtualPrivateCloud = &cvm.VirtualPrivateCloud{ VpcId: &vpc_id, SubnetId: &subnet_id, } TRAFFIC_POSTPAID_BY_HOUR := "TRAFFIC_POSTPAID_BY_HOUR" if s.AssociatePublicIpAddress { req.InternetAccessible = &cvm.InternetAccessible{ InternetChargeType: &TRAFFIC_POSTPAID_BY_HOUR, InternetMaxBandwidthOut: &s.InternetMaxBandwidthOut, } } req.InstanceName = &s.InstanceName loginSettings := cvm.LoginSettings{} if password != "" { loginSettings.Password = &password } if config.Comm.SSHKeyPairName != "" { loginSettings.KeyIds = []*string{&config.Comm.SSHKeyPairName} } req.LoginSettings = &loginSettings req.SecurityGroupIds = []*string{&security_group_id} req.ClientToken = &s.InstanceName req.HostName = &s.HostName req.UserData = &userData var tags []*cvm.Tag for k, v := range s.Tags { tags = append(tags, &cvm.Tag{ Key: &k, Value: &v, }) } resourceType := "instance" if len(tags) > 0 { req.TagSpecification = []*cvm.TagSpecification{ &cvm.TagSpecification{ ResourceType: &resourceType, Tags: tags, }, } } resp, err := client.RunInstances(req) if err != nil { err := fmt.Errorf("create instance failed: %s", err.Error()) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } if len(resp.Response.InstanceIdSet) != 1 { err := fmt.Errorf("create instance failed: %d instance(s) created", len(resp.Response.InstanceIdSet)) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } s.instanceId = *resp.Response.InstanceIdSet[0] err = WaitForInstance(client, s.instanceId, "RUNNING", 1800) if err != nil { err := fmt.Errorf("wait instance launch failed: %s", err.Error()) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } describeReq := cvm.NewDescribeInstancesRequest() describeReq.InstanceIds = []*string{&s.instanceId} describeResp, err := client.DescribeInstances(describeReq) if err != nil { err := fmt.Errorf("wait instance launch failed: %s", err.Error()) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } state.Put("instance", describeResp.Response.InstanceSet[0]) return multistep.ActionContinue } func (s *stepRunInstance) getUserData(state multistep.StateBag) (string, error) { userData := s.UserData if userData == "" && s.UserDataFile != "" { data, err := ioutil.ReadFile(s.UserDataFile) if err != nil { return "", err } userData = string(data) } userData = base64.StdEncoding.EncodeToString([]byte(userData)) log.Printf(fmt.Sprintf("user_data: %s", userData)) return userData, nil } func (s *stepRunInstance) Cleanup(state multistep.StateBag) { if s.instanceId == "" { return } MessageClean(state, "Instance") client := state.Get("cvm_client").(*cvm.Client) ui := state.Get("ui").(packer.Ui) req := cvm.NewTerminateInstancesRequest() 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) return err }) if err != nil { ui.Error(fmt.Sprintf("terminate instance(%s) failed: %s", s.instanceId, err.Error())) } }