github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/builtin/providers/cloudstack/resource_cloudstack_instance.go (about) 1 package cloudstack 2 3 import ( 4 "crypto/sha1" 5 "encoding/base64" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "log" 10 "strings" 11 12 "github.com/hashicorp/terraform/helper/schema" 13 "github.com/xanzy/go-cloudstack/cloudstack" 14 ) 15 16 func resourceCloudStackInstance() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceCloudStackInstanceCreate, 19 Read: resourceCloudStackInstanceRead, 20 Update: resourceCloudStackInstanceUpdate, 21 Delete: resourceCloudStackInstanceDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "name": &schema.Schema{ 25 Type: schema.TypeString, 26 Optional: true, 27 Computed: true, 28 }, 29 30 "display_name": &schema.Schema{ 31 Type: schema.TypeString, 32 Optional: true, 33 Computed: true, 34 }, 35 36 "service_offering": &schema.Schema{ 37 Type: schema.TypeString, 38 Required: true, 39 }, 40 41 "network_id": &schema.Schema{ 42 Type: schema.TypeString, 43 Optional: true, 44 Computed: true, 45 ForceNew: true, 46 }, 47 48 "network": &schema.Schema{ 49 Type: schema.TypeString, 50 Optional: true, 51 ForceNew: true, 52 Deprecated: "Please use the `network_id` field instead", 53 }, 54 55 "ip_address": &schema.Schema{ 56 Type: schema.TypeString, 57 Optional: true, 58 Computed: true, 59 ForceNew: true, 60 }, 61 62 "ipaddress": &schema.Schema{ 63 Type: schema.TypeString, 64 Optional: true, 65 Computed: true, 66 ForceNew: true, 67 Deprecated: "Please use the `ip_address` field instead", 68 }, 69 70 "template": &schema.Schema{ 71 Type: schema.TypeString, 72 Required: true, 73 ForceNew: true, 74 }, 75 76 "project": &schema.Schema{ 77 Type: schema.TypeString, 78 Optional: true, 79 ForceNew: true, 80 }, 81 82 "zone": &schema.Schema{ 83 Type: schema.TypeString, 84 Required: true, 85 ForceNew: true, 86 }, 87 88 "keypair": &schema.Schema{ 89 Type: schema.TypeString, 90 Optional: true, 91 }, 92 93 "user_data": &schema.Schema{ 94 Type: schema.TypeString, 95 Optional: true, 96 ForceNew: true, 97 StateFunc: func(v interface{}) string { 98 switch v.(type) { 99 case string: 100 hash := sha1.Sum([]byte(v.(string))) 101 return hex.EncodeToString(hash[:]) 102 default: 103 return "" 104 } 105 }, 106 }, 107 108 "expunge": &schema.Schema{ 109 Type: schema.TypeBool, 110 Optional: true, 111 Default: false, 112 }, 113 114 "group": &schema.Schema{ 115 Type: schema.TypeString, 116 Optional: true, 117 Computed: true, 118 }, 119 }, 120 } 121 } 122 123 func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error { 124 cs := meta.(*cloudstack.CloudStackClient) 125 126 // Retrieve the service_offering ID 127 serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string)) 128 if e != nil { 129 return e.Error() 130 } 131 132 // Retrieve the zone ID 133 zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string)) 134 if e != nil { 135 return e.Error() 136 } 137 138 // Retrieve the zone object 139 zone, _, err := cs.Zone.GetZoneByID(zoneid) 140 if err != nil { 141 return err 142 } 143 144 // Retrieve the template ID 145 templateid, e := retrieveTemplateID(cs, zone.Id, d.Get("template").(string)) 146 if e != nil { 147 return e.Error() 148 } 149 150 // Create a new parameter struct 151 p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id) 152 153 // Set the name 154 name, hasName := d.GetOk("name") 155 if hasName { 156 p.SetName(name.(string)) 157 } 158 159 // Set the display name 160 if displayname, ok := d.GetOk("display_name"); ok { 161 p.SetDisplayname(displayname.(string)) 162 } else if hasName { 163 p.SetDisplayname(name.(string)) 164 } 165 166 if zone.Networktype == "Advanced" { 167 network, ok := d.GetOk("network_id") 168 if !ok { 169 network, ok = d.GetOk("network") 170 } 171 if !ok { 172 return errors.New( 173 "Either `network_id` or [deprecated] `network` must be provided when using a zone with network type `advanced`.") 174 } 175 176 // Retrieve the network ID 177 networkid, e := retrieveID( 178 cs, 179 "network", 180 network.(string), 181 cloudstack.WithProject(d.Get("project").(string)), 182 ) 183 if e != nil { 184 return e.Error() 185 } 186 187 // Set the default network ID 188 p.SetNetworkids([]string{networkid}) 189 } 190 191 // If there is a ipaddres supplied, add it to the parameter struct 192 ipaddress, ok := d.GetOk("ip_address") 193 if !ok { 194 ipaddress, ok = d.GetOk("ipaddress") 195 } 196 if ok { 197 p.SetIpaddress(ipaddress.(string)) 198 } 199 200 // If there is a project supplied, we retrieve and set the project id 201 if err := setProjectid(p, cs, d); err != nil { 202 return err 203 } 204 205 // If a keypair is supplied, add it to the parameter struct 206 if keypair, ok := d.GetOk("keypair"); ok { 207 p.SetKeypair(keypair.(string)) 208 } 209 210 // If the user data contains any info, it needs to be base64 encoded and 211 // added to the parameter struct 212 if userData, ok := d.GetOk("user_data"); ok { 213 ud := base64.StdEncoding.EncodeToString([]byte(userData.(string))) 214 215 // deployVirtualMachine uses POST by default, so max userdata is 32K 216 maxUD := 32768 217 218 if cs.HTTPGETOnly { 219 // deployVirtualMachine using GET instead, so max userdata is 2K 220 maxUD = 2048 221 } 222 223 if len(ud) > maxUD { 224 return fmt.Errorf( 225 "The supplied user_data contains %d bytes after encoding, "+ 226 "this exeeds the limit of %d bytes", len(ud), maxUD) 227 } 228 229 p.SetUserdata(ud) 230 } 231 232 // If there is a group supplied, add it to the parameter struct 233 if group, ok := d.GetOk("group"); ok { 234 p.SetGroup(group.(string)) 235 } 236 237 // Create the new instance 238 r, err := cs.VirtualMachine.DeployVirtualMachine(p) 239 if err != nil { 240 return fmt.Errorf("Error creating the new instance %s: %s", name, err) 241 } 242 243 d.SetId(r.Id) 244 245 // Set the connection info for any configured provisioners 246 d.SetConnInfo(map[string]string{ 247 "host": r.Nic[0].Ipaddress, 248 "password": r.Password, 249 }) 250 251 return resourceCloudStackInstanceRead(d, meta) 252 } 253 254 func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) error { 255 cs := meta.(*cloudstack.CloudStackClient) 256 257 // Get the virtual machine details 258 vm, count, err := cs.VirtualMachine.GetVirtualMachineByID( 259 d.Id(), 260 cloudstack.WithProject(d.Get("project").(string)), 261 ) 262 if err != nil { 263 if count == 0 { 264 log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string)) 265 d.SetId("") 266 return nil 267 } 268 269 return err 270 } 271 272 // Update the config 273 d.Set("name", vm.Name) 274 d.Set("display_name", vm.Displayname) 275 d.Set("network_id", vm.Nic[0].Networkid) 276 d.Set("ip_address", vm.Nic[0].Ipaddress) 277 d.Set("group", vm.Group) 278 279 setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid) 280 setValueOrID(d, "template", vm.Templatename, vm.Templateid) 281 setValueOrID(d, "project", vm.Project, vm.Projectid) 282 setValueOrID(d, "zone", vm.Zonename, vm.Zoneid) 283 284 return nil 285 } 286 287 func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 288 cs := meta.(*cloudstack.CloudStackClient) 289 d.Partial(true) 290 291 name := d.Get("name").(string) 292 293 // Check if the display name is changed and if so, update the virtual machine 294 if d.HasChange("display_name") { 295 log.Printf("[DEBUG] Display name changed for %s, starting update", name) 296 297 // Create a new parameter struct 298 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 299 300 // Set the new display name 301 p.SetDisplayname(d.Get("display_name").(string)) 302 303 // Update the display name 304 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 305 if err != nil { 306 return fmt.Errorf( 307 "Error updating the display name for instance %s: %s", name, err) 308 } 309 310 d.SetPartial("display_name") 311 } 312 313 // Check if the group is changed and if so, update the virtual machine 314 if d.HasChange("group") { 315 log.Printf("[DEBUG] Group changed for %s, starting update", name) 316 317 // Create a new parameter struct 318 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 319 320 // Set the new group 321 p.SetGroup(d.Get("group").(string)) 322 323 // Update the display name 324 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 325 if err != nil { 326 return fmt.Errorf( 327 "Error updating the group for instance %s: %s", name, err) 328 } 329 330 d.SetPartial("group") 331 } 332 333 // Attributes that require reboot to update 334 if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("keypair") { 335 // Before we can actually make these changes, the virtual machine must be stopped 336 _, err := cs.VirtualMachine.StopVirtualMachine( 337 cs.VirtualMachine.NewStopVirtualMachineParams(d.Id())) 338 if err != nil { 339 return fmt.Errorf( 340 "Error stopping instance %s before making changes: %s", name, err) 341 } 342 343 // Check if the name has changed and if so, update the name 344 if d.HasChange("name") { 345 log.Printf("[DEBUG] Name for %s changed to %s, starting update", d.Id(), name) 346 347 // Create a new parameter struct 348 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 349 350 // Set the new name 351 p.SetName(name) 352 353 // Update the display name 354 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 355 if err != nil { 356 return fmt.Errorf( 357 "Error updating the name for instance %s: %s", name, err) 358 } 359 360 d.SetPartial("name") 361 } 362 363 // Check if the service offering is changed and if so, update the offering 364 if d.HasChange("service_offering") { 365 log.Printf("[DEBUG] Service offering changed for %s, starting update", name) 366 367 // Retrieve the service_offering ID 368 serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string)) 369 if e != nil { 370 return e.Error() 371 } 372 373 // Create a new parameter struct 374 p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid) 375 376 // Change the service offering 377 _, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p) 378 if err != nil { 379 return fmt.Errorf( 380 "Error changing the service offering for instance %s: %s", name, err) 381 } 382 d.SetPartial("service_offering") 383 } 384 385 if d.HasChange("keypair") { 386 log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name) 387 388 p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string)) 389 390 // Change the ssh keypair 391 _, err = cs.SSH.ResetSSHKeyForVirtualMachine(p) 392 if err != nil { 393 return fmt.Errorf( 394 "Error changing the SSH keypair for instance %s: %s", name, err) 395 } 396 d.SetPartial("keypair") 397 } 398 399 // Start the virtual machine again 400 _, err = cs.VirtualMachine.StartVirtualMachine( 401 cs.VirtualMachine.NewStartVirtualMachineParams(d.Id())) 402 if err != nil { 403 return fmt.Errorf( 404 "Error starting instance %s after making changes", name) 405 } 406 } 407 408 d.Partial(false) 409 return resourceCloudStackInstanceRead(d, meta) 410 } 411 412 func resourceCloudStackInstanceDelete(d *schema.ResourceData, meta interface{}) error { 413 cs := meta.(*cloudstack.CloudStackClient) 414 415 // Create a new parameter struct 416 p := cs.VirtualMachine.NewDestroyVirtualMachineParams(d.Id()) 417 418 if d.Get("expunge").(bool) { 419 p.SetExpunge(true) 420 } 421 422 log.Printf("[INFO] Destroying instance: %s", d.Get("name").(string)) 423 if _, err := cs.VirtualMachine.DestroyVirtualMachine(p); err != nil { 424 // This is a very poor way to be told the ID does no longer exist :( 425 if strings.Contains(err.Error(), fmt.Sprintf( 426 "Invalid parameter id value=%s due to incorrect long value format, "+ 427 "or entity does not exist", d.Id())) { 428 return nil 429 } 430 431 return fmt.Errorf("Error destroying instance: %s", err) 432 } 433 434 return nil 435 }