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 }