2019-08-09 03:00:23 -04:00
|
|
|
package jdcloud
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2019-12-17 05:25:56 -05:00
|
|
|
"regexp"
|
|
|
|
"time"
|
|
|
|
|
2019-08-09 03:00:23 -04:00
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
|
|
"github.com/hashicorp/packer/packer"
|
|
|
|
"github.com/jdcloud-api/jdcloud-sdk-go/core"
|
|
|
|
"github.com/jdcloud-api/jdcloud-sdk-go/services/vm/apis"
|
|
|
|
vm "github.com/jdcloud-api/jdcloud-sdk-go/services/vm/models"
|
|
|
|
vpcApis "github.com/jdcloud-api/jdcloud-sdk-go/services/vpc/apis"
|
|
|
|
vpcClient "github.com/jdcloud-api/jdcloud-sdk-go/services/vpc/client"
|
|
|
|
vpc "github.com/jdcloud-api/jdcloud-sdk-go/services/vpc/models"
|
|
|
|
)
|
|
|
|
|
|
|
|
type stepCreateJDCloudInstance struct {
|
|
|
|
InstanceSpecConfig *JDCloudInstanceSpecConfig
|
|
|
|
CredentialConfig *JDCloudCredentialConfig
|
|
|
|
ui packer.Ui
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stepCreateJDCloudInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
|
|
|
|
|
|
|
privateKey := s.InstanceSpecConfig.Comm.SSHPrivateKey
|
|
|
|
keyName := s.InstanceSpecConfig.Comm.SSHKeyPairName
|
|
|
|
password := s.InstanceSpecConfig.Comm.SSHPassword
|
|
|
|
s.ui = state.Get("ui").(packer.Ui)
|
|
|
|
s.ui.Say("Creating instances")
|
|
|
|
|
|
|
|
instanceSpec := vm.InstanceSpec{
|
|
|
|
Az: &s.CredentialConfig.Az,
|
|
|
|
InstanceType: &s.InstanceSpecConfig.InstanceType,
|
|
|
|
ImageId: &s.InstanceSpecConfig.ImageId,
|
|
|
|
Name: s.InstanceSpecConfig.InstanceName,
|
|
|
|
PrimaryNetworkInterface: &vm.InstanceNetworkInterfaceAttachmentSpec{
|
|
|
|
NetworkInterface: &vpc.NetworkInterfaceSpec{
|
|
|
|
SubnetId: s.InstanceSpecConfig.SubnetId,
|
|
|
|
Az: &s.CredentialConfig.Az,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(password) > 0 {
|
|
|
|
instanceSpec.Password = &password
|
|
|
|
}
|
|
|
|
if len(keyName) > 0 && len(privateKey) > 0 {
|
|
|
|
instanceSpec.KeyNames = []string{keyName}
|
|
|
|
}
|
|
|
|
|
|
|
|
req := apis.NewCreateInstancesRequest(Region, &instanceSpec)
|
|
|
|
resp, err := VmClient.CreateInstances(req)
|
|
|
|
|
|
|
|
if err != nil || resp.Error.Code != FINE {
|
|
|
|
err := fmt.Errorf("Error creating instance, error-%v response:%v", err, resp)
|
|
|
|
s.ui.Error(err.Error())
|
|
|
|
return multistep.ActionHalt
|
|
|
|
}
|
|
|
|
|
|
|
|
s.InstanceSpecConfig.InstanceId = resp.Result.InstanceIds[0]
|
|
|
|
instanceInterface, err := InstanceStatusRefresher(s.InstanceSpecConfig.InstanceId, []string{VM_PENDING, VM_STARTING}, []string{VM_RUNNING})
|
|
|
|
if err != nil {
|
|
|
|
s.ui.Error(err.Error())
|
|
|
|
return multistep.ActionHalt
|
|
|
|
}
|
|
|
|
|
|
|
|
instance := instanceInterface.(vm.Instance)
|
|
|
|
privateIpAddress := instance.PrivateIpAddress
|
|
|
|
networkInterfaceId := instance.PrimaryNetworkInterface.NetworkInterface.NetworkInterfaceId
|
|
|
|
|
|
|
|
s.ui.Message("Creating public-ip")
|
|
|
|
s.InstanceSpecConfig.PublicIpId, err = createElasticIp(state)
|
|
|
|
if err != nil {
|
|
|
|
s.ui.Error(err.Error())
|
|
|
|
return multistep.ActionHalt
|
|
|
|
}
|
|
|
|
|
|
|
|
s.ui.Message("Associating public-ip with instance")
|
|
|
|
err = associatePublicIp(networkInterfaceId, s.InstanceSpecConfig.PublicIpId, privateIpAddress)
|
|
|
|
if err != nil {
|
|
|
|
s.ui.Error(err.Error())
|
|
|
|
return multistep.ActionHalt
|
|
|
|
}
|
|
|
|
|
|
|
|
req_ := vpcApis.NewDescribeElasticIpRequest(Region, s.InstanceSpecConfig.PublicIpId)
|
|
|
|
eip, err := VpcClient.DescribeElasticIp(req_)
|
|
|
|
if err != nil || eip.Error.Code != FINE {
|
|
|
|
s.ui.Error(fmt.Sprintf("[ERROR] Failed in getting eip,error:%v \n response:%v", err, eip))
|
|
|
|
return multistep.ActionHalt
|
|
|
|
}
|
|
|
|
|
|
|
|
s.InstanceSpecConfig.PublicIpAddress = eip.Result.ElasticIp.ElasticIpAddress
|
|
|
|
state.Put("eip", s.InstanceSpecConfig.PublicIpAddress)
|
|
|
|
s.ui.Message(fmt.Sprintf(
|
|
|
|
"Hi, we have created the instance, its name=%v , "+
|
|
|
|
"its id=%v, "+
|
|
|
|
"and its eip=%v :) ", instance.InstanceName, s.InstanceSpecConfig.InstanceId, eip.Result.ElasticIp.ElasticIpAddress))
|
2019-12-13 14:57:01 -05:00
|
|
|
// instance_id is the generic term used so that users can have access to the
|
|
|
|
// instance id inside of the provisioners, used in step_provision.
|
|
|
|
state.Put("instance_id", s.InstanceSpecConfig.InstanceId)
|
2019-08-09 03:00:23 -04:00
|
|
|
return multistep.ActionContinue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete created resources {instance,ip} on error
|
|
|
|
func (s *stepCreateJDCloudInstance) Cleanup(state multistep.StateBag) {
|
|
|
|
|
|
|
|
if s.InstanceSpecConfig.PublicIpId != "" {
|
|
|
|
|
|
|
|
req := vpcApis.NewDeleteElasticIpRequest(Region, s.InstanceSpecConfig.PublicIpId)
|
|
|
|
|
|
|
|
_ = Retry(time.Minute, func() *RetryError {
|
|
|
|
_, err := VpcClient.DeleteElasticIp(req)
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if connectionError(err) {
|
|
|
|
return RetryableError(err)
|
|
|
|
} else {
|
|
|
|
return NonRetryableError(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.InstanceSpecConfig.InstanceId != "" {
|
|
|
|
|
|
|
|
req := apis.NewDeleteInstanceRequest(Region, s.InstanceSpecConfig.InstanceId)
|
|
|
|
_ = Retry(time.Minute, func() *RetryError {
|
|
|
|
_, err := VmClient.DeleteInstance(req)
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if connectionError(err) {
|
|
|
|
return RetryableError(err)
|
|
|
|
} else {
|
|
|
|
return NonRetryableError(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createElasticIp(state multistep.StateBag) (string, error) {
|
|
|
|
|
|
|
|
generalConfig := state.Get("config").(Config)
|
|
|
|
regionId := generalConfig.RegionId
|
|
|
|
credential := core.NewCredentials(generalConfig.AccessKey, generalConfig.SecretKey)
|
|
|
|
vpcclient := vpcClient.NewVpcClient(credential)
|
|
|
|
|
|
|
|
req := vpcApis.NewCreateElasticIpsRequest(regionId, 1, &vpc.ElasticIpSpec{
|
|
|
|
BandwidthMbps: 1,
|
|
|
|
Provider: "bgp",
|
|
|
|
})
|
|
|
|
|
|
|
|
resp, err := vpcclient.CreateElasticIps(req)
|
|
|
|
|
|
|
|
if err != nil || resp.Error.Code != 0 {
|
|
|
|
return "", fmt.Errorf("[ERROR] Failed in creating new publicIp, Error-%v, Response:%v", err, resp)
|
|
|
|
}
|
|
|
|
return resp.Result.ElasticIpIds[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func associatePublicIp(networkInterfaceId string, eipId string, privateIpAddress string) error {
|
|
|
|
req := vpcApis.NewAssociateElasticIpRequest(Region, networkInterfaceId)
|
|
|
|
req.ElasticIpId = &eipId
|
|
|
|
req.PrivateIpAddress = &privateIpAddress
|
|
|
|
resp, err := VpcClient.AssociateElasticIp(req)
|
|
|
|
if err != nil || resp.Error.Code != FINE {
|
|
|
|
return fmt.Errorf("[ERROR] Failed in associating publicIp, Error-%v, Response:%v", err, resp)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func instanceHost(state multistep.StateBag) (string, error) {
|
|
|
|
return state.Get("eip").(string), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func InstanceStatusRefresher(id string, pending, target []string) (instance interface{}, err error) {
|
|
|
|
|
|
|
|
stateConf := &StateChangeConf{
|
|
|
|
Pending: pending,
|
|
|
|
Target: target,
|
|
|
|
Refresh: instanceStatusRefresher(id),
|
|
|
|
Delay: 3 * time.Second,
|
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
MinTimeout: 1 * time.Second,
|
|
|
|
}
|
|
|
|
if instance, err = stateConf.WaitForState(); err != nil {
|
|
|
|
return nil, fmt.Errorf("[ERROR] Failed in creating instance ,err message:%v", err)
|
|
|
|
}
|
|
|
|
return instance, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func instanceStatusRefresher(instanceId string) StateRefreshFunc {
|
|
|
|
|
|
|
|
return func() (instance interface{}, status string, err error) {
|
|
|
|
|
|
|
|
err = Retry(time.Minute, func() *RetryError {
|
|
|
|
|
|
|
|
req := apis.NewDescribeInstanceRequest(Region, instanceId)
|
|
|
|
resp, err := VmClient.DescribeInstance(req)
|
|
|
|
|
|
|
|
if err == nil && resp.Error.Code == FINE {
|
|
|
|
instance = resp.Result.Instance
|
|
|
|
status = resp.Result.Instance.Status
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
instance = nil
|
|
|
|
status = ""
|
|
|
|
if connectionError(err) {
|
|
|
|
return RetryableError(err)
|
|
|
|
} else {
|
|
|
|
return NonRetryableError(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return instance, status, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func connectionError(e error) bool {
|
|
|
|
|
|
|
|
if e == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ok, _ := regexp.MatchString(CONNECT_FAILED, e.Error())
|
|
|
|
return ok
|
|
|
|
}
|