github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/cloudstack/step_create_instance.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/packer/common"
    11  	"github.com/hashicorp/packer/packer"
    12  	"github.com/hashicorp/packer/template/interpolate"
    13  	"github.com/mitchellh/multistep"
    14  	"github.com/xanzy/go-cloudstack/cloudstack"
    15  )
    16  
    17  // userDataTemplateData represents variables for user_data interpolation
    18  type userDataTemplateData struct {
    19  	HTTPIP   string
    20  	HTTPPort uint
    21  }
    22  
    23  // stepCreateInstance represents a Packer build step that creates CloudStack instances.
    24  type stepCreateInstance struct {
    25  	Debug bool
    26  	Ctx   interpolate.Context
    27  }
    28  
    29  // Run executes the Packer build step that creates a CloudStack instance.
    30  func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
    31  	client := state.Get("client").(*cloudstack.CloudStackClient)
    32  	config := state.Get("config").(*Config)
    33  	ui := state.Get("ui").(packer.Ui)
    34  
    35  	ui.Say("Creating instance...")
    36  
    37  	// Create a new parameter struct.
    38  	p := client.VirtualMachine.NewDeployVirtualMachineParams(
    39  		config.ServiceOffering,
    40  		state.Get("source").(string),
    41  		config.Zone,
    42  	)
    43  
    44  	// Configure the instance.
    45  	p.SetName(config.InstanceName)
    46  	p.SetDisplayname("Created by Packer")
    47  
    48  	if keypair, ok := state.GetOk("keypair"); ok {
    49  		p.SetKeypair(keypair.(string))
    50  	}
    51  
    52  	if securitygroups, ok := state.GetOk("security_groups"); ok {
    53  		p.SetSecuritygroupids(securitygroups.([]string))
    54  	}
    55  
    56  	// If we use an ISO, configure the disk offering.
    57  	if config.SourceISO != "" {
    58  		p.SetDiskofferingid(config.DiskOffering)
    59  		p.SetHypervisor(config.Hypervisor)
    60  	}
    61  
    62  	// If we use a template, set the root disk size.
    63  	if config.SourceTemplate != "" && config.DiskSize > 0 {
    64  		p.SetRootdisksize(config.DiskSize)
    65  	}
    66  
    67  	// Retrieve the zone object.
    68  	zone, _, err := client.Zone.GetZoneByID(config.Zone)
    69  	if err != nil {
    70  		err := fmt.Errorf("Failed to get zone %s by ID: %s", config.Zone, err)
    71  		state.Put("error", err)
    72  		ui.Error(err.Error())
    73  		return multistep.ActionHalt
    74  	}
    75  
    76  	if zone.Networktype == "Advanced" {
    77  		// Set the network ID's.
    78  		p.SetNetworkids([]string{config.Network})
    79  	}
    80  
    81  	// If there is a project supplied, set the project id.
    82  	if config.Project != "" {
    83  		p.SetProjectid(config.Project)
    84  	}
    85  
    86  	if config.UserData != "" {
    87  		httpPort := state.Get("http_port").(uint)
    88  		httpIP, err := hostIP()
    89  		if err != nil {
    90  			err := fmt.Errorf("Failed to determine host IP: %s", err)
    91  			state.Put("error", err)
    92  			ui.Error(err.Error())
    93  			return multistep.ActionHalt
    94  		}
    95  		common.SetHTTPIP(httpIP)
    96  
    97  		s.Ctx.Data = &userDataTemplateData{
    98  			httpIP,
    99  			httpPort,
   100  		}
   101  
   102  		ud, err := s.generateUserData(config.UserData, config.HTTPGetOnly)
   103  		if err != nil {
   104  			state.Put("error", err)
   105  			ui.Error(err.Error())
   106  			return multistep.ActionHalt
   107  		}
   108  
   109  		p.SetUserdata(ud)
   110  	}
   111  
   112  	// Create the new instance.
   113  	instance, err := client.VirtualMachine.DeployVirtualMachine(p)
   114  	if err != nil {
   115  		err := fmt.Errorf("Error creating new instance %s: %s", config.InstanceName, err)
   116  		state.Put("error", err)
   117  		ui.Error(err.Error())
   118  		return multistep.ActionHalt
   119  	}
   120  
   121  	ui.Message("Instance has been created!")
   122  
   123  	// In debug-mode, we output the password
   124  	if s.Debug {
   125  		ui.Message(fmt.Sprintf(
   126  			"Password (since debug is enabled) \"%s\"", instance.Password))
   127  	}
   128  
   129  	// Set the auto generated password if a password was not explicitly configured.
   130  	switch config.Comm.Type {
   131  	case "ssh":
   132  		if config.Comm.SSHPassword == "" {
   133  			config.Comm.SSHPassword = instance.Password
   134  		}
   135  	case "winrm":
   136  		if config.Comm.WinRMPassword == "" {
   137  			config.Comm.WinRMPassword = instance.Password
   138  		}
   139  	}
   140  
   141  	// Set the host address when using the local IP address to connect.
   142  	if config.UseLocalIPAddress {
   143  		state.Put("ipaddress", instance.Nic[0].Ipaddress)
   144  	}
   145  
   146  	// Store the instance ID so we can remove it later.
   147  	state.Put("instance_id", instance.Id)
   148  
   149  	return multistep.ActionContinue
   150  }
   151  
   152  // Cleanup any resources that may have been created during the Run phase.
   153  func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
   154  	client := state.Get("client").(*cloudstack.CloudStackClient)
   155  	config := state.Get("config").(*Config)
   156  	ui := state.Get("ui").(packer.Ui)
   157  
   158  	instanceID, ok := state.Get("instance_id").(string)
   159  	if !ok || instanceID == "" {
   160  		return
   161  	}
   162  
   163  	// Create a new parameter struct.
   164  	p := client.VirtualMachine.NewDestroyVirtualMachineParams(instanceID)
   165  
   166  	ui.Say("Deleting instance...")
   167  	if _, err := client.VirtualMachine.DestroyVirtualMachine(p); err != nil {
   168  		// This is a very poor way to be told the ID does no longer exist :(
   169  		if strings.Contains(err.Error(), fmt.Sprintf(
   170  			"Invalid parameter id value=%s due to incorrect long value format, "+
   171  				"or entity does not exist", instanceID)) {
   172  			return
   173  		}
   174  
   175  		ui.Error(fmt.Sprintf("Error destroying instance. Please destroy it manually.\n\n"+
   176  			"\tName: %s\n"+
   177  			"\tError: %s", config.InstanceName, err))
   178  		return
   179  	}
   180  
   181  	// We could expunge the VM while destroying it, but if the user doesn't have
   182  	// rights that single call could error out leaving the VM running. So but
   183  	// splitting these calls we make sure the VM is always deleted, even when the
   184  	// expunge fails.
   185  	if config.Expunge {
   186  		// Create a new parameter struct.
   187  		p := client.VirtualMachine.NewExpungeVirtualMachineParams(instanceID)
   188  
   189  		ui.Say("Expunging instance...")
   190  		if _, err := client.VirtualMachine.ExpungeVirtualMachine(p); err != nil {
   191  			// This is a very poor way to be told the ID does no longer exist :(
   192  			if strings.Contains(err.Error(), fmt.Sprintf(
   193  				"Invalid parameter id value=%s due to incorrect long value format, "+
   194  					"or entity does not exist", instanceID)) {
   195  				return
   196  			}
   197  
   198  			ui.Error(fmt.Sprintf("Error expunging instance. Please expunge it manually.\n\n"+
   199  				"\tName: %s\n"+
   200  				"\tError: %s", config.InstanceName, err))
   201  			return
   202  		}
   203  	}
   204  
   205  	ui.Message("Instance has been deleted!")
   206  	return
   207  }
   208  
   209  // generateUserData returns the user data as a base64 encoded string.
   210  func (s *stepCreateInstance) generateUserData(userData string, httpGETOnly bool) (string, error) {
   211  	renderedUserData, err := interpolate.Render(userData, &s.Ctx)
   212  	if err != nil {
   213  		return "", fmt.Errorf("Error rendering user_data: %s", err)
   214  	}
   215  
   216  	ud := base64.StdEncoding.EncodeToString([]byte(renderedUserData))
   217  
   218  	// DeployVirtualMachine uses POST by default which allows 32K of
   219  	// userdata. If using GET instead the userdata is limited to 2K.
   220  	maxUD := 32768
   221  	if httpGETOnly {
   222  		maxUD = 2048
   223  	}
   224  
   225  	if len(ud) > maxUD {
   226  		return "", fmt.Errorf(
   227  			"The supplied user_data contains %d bytes after encoding, "+
   228  				"this exeeds the limit of %d bytes", len(ud), maxUD)
   229  	}
   230  
   231  	return ud, nil
   232  }
   233  
   234  func hostIP() (string, error) {
   235  	addrs, err := net.InterfaceAddrs()
   236  	if err != nil {
   237  		return "", err
   238  	}
   239  
   240  	for _, addr := range addrs {
   241  		if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
   242  			if ipnet.IP.To4() != nil {
   243  				return ipnet.IP.String(), nil
   244  			}
   245  		}
   246  	}
   247  
   248  	return "", errors.New("No host IP found")
   249  }