github.com/danrjohnson/terraform@v0.7.0-rc2.0.20160627135212-d0fc1fa086ff/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 "affinity_group_ids": &schema.Schema{ 77 Type: schema.TypeSet, 78 Optional: true, 79 Elem: &schema.Schema{Type: schema.TypeString}, 80 Set: schema.HashString, 81 ConflictsWith: []string{"affinity_group_names"}, 82 }, 83 84 "affinity_group_names": &schema.Schema{ 85 Type: schema.TypeSet, 86 Optional: true, 87 Elem: &schema.Schema{Type: schema.TypeString}, 88 Set: schema.HashString, 89 ConflictsWith: []string{"affinity_group_ids"}, 90 }, 91 92 "security_group_ids": &schema.Schema{ 93 Type: schema.TypeSet, 94 Optional: true, 95 ForceNew: true, 96 Elem: &schema.Schema{Type: schema.TypeString}, 97 Set: schema.HashString, 98 ConflictsWith: []string{"security_group_names"}, 99 }, 100 101 "security_group_names": &schema.Schema{ 102 Type: schema.TypeSet, 103 Optional: true, 104 ForceNew: true, 105 Elem: &schema.Schema{Type: schema.TypeString}, 106 Set: schema.HashString, 107 ConflictsWith: []string{"security_group_ids"}, 108 }, 109 110 "project": &schema.Schema{ 111 Type: schema.TypeString, 112 Optional: true, 113 ForceNew: true, 114 }, 115 116 "zone": &schema.Schema{ 117 Type: schema.TypeString, 118 Required: true, 119 ForceNew: true, 120 }, 121 122 "keypair": &schema.Schema{ 123 Type: schema.TypeString, 124 Optional: true, 125 }, 126 127 "user_data": &schema.Schema{ 128 Type: schema.TypeString, 129 Optional: true, 130 ForceNew: true, 131 StateFunc: func(v interface{}) string { 132 switch v.(type) { 133 case string: 134 hash := sha1.Sum([]byte(v.(string))) 135 return hex.EncodeToString(hash[:]) 136 default: 137 return "" 138 } 139 }, 140 }, 141 142 "expunge": &schema.Schema{ 143 Type: schema.TypeBool, 144 Optional: true, 145 Default: false, 146 }, 147 148 "group": &schema.Schema{ 149 Type: schema.TypeString, 150 Optional: true, 151 Computed: true, 152 }, 153 154 "root_disk_size": &schema.Schema{ 155 Type: schema.TypeInt, 156 Optional: true, 157 ForceNew: true, 158 }, 159 }, 160 } 161 } 162 163 func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error { 164 cs := meta.(*cloudstack.CloudStackClient) 165 166 // Retrieve the service_offering ID 167 serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string)) 168 if e != nil { 169 return e.Error() 170 } 171 172 // Retrieve the zone ID 173 zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string)) 174 if e != nil { 175 return e.Error() 176 } 177 178 // Retrieve the zone object 179 zone, _, err := cs.Zone.GetZoneByID(zoneid) 180 if err != nil { 181 return err 182 } 183 184 // Retrieve the template ID 185 templateid, e := retrieveTemplateID(cs, zone.Id, d.Get("template").(string)) 186 if e != nil { 187 return e.Error() 188 } 189 190 // Create a new parameter struct 191 p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id) 192 193 // Set the name 194 name, hasName := d.GetOk("name") 195 if hasName { 196 p.SetName(name.(string)) 197 } 198 199 // Set the display name 200 if displayname, ok := d.GetOk("display_name"); ok { 201 p.SetDisplayname(displayname.(string)) 202 } else if hasName { 203 p.SetDisplayname(name.(string)) 204 } 205 206 if zone.Networktype == "Advanced" { 207 network, ok := d.GetOk("network_id") 208 if !ok { 209 network, ok = d.GetOk("network") 210 } 211 if !ok { 212 return errors.New( 213 "Either `network_id` or [deprecated] `network` must be provided when using a zone with network type `advanced`.") 214 } 215 216 // Retrieve the network ID 217 networkid, e := retrieveID( 218 cs, 219 "network", 220 network.(string), 221 cloudstack.WithProject(d.Get("project").(string)), 222 ) 223 if e != nil { 224 return e.Error() 225 } 226 227 // Set the default network ID 228 p.SetNetworkids([]string{networkid}) 229 } 230 231 // If there is a ipaddres supplied, add it to the parameter struct 232 ipaddress, ok := d.GetOk("ip_address") 233 if !ok { 234 ipaddress, ok = d.GetOk("ipaddress") 235 } 236 if ok { 237 p.SetIpaddress(ipaddress.(string)) 238 } 239 240 if ags := d.Get("affinity_group_ids").(*schema.Set); ags.Len() > 0 { 241 var groups []string 242 243 for _, group := range ags.List() { 244 groups = append(groups, group.(string)) 245 } 246 247 p.SetAffinitygroupids(groups) 248 } 249 250 // If there is a affinity_group_names supplied, add it to the parameter struct 251 if agns := d.Get("affinity_group_names").(*schema.Set); agns.Len() > 0 { 252 var groups []string 253 254 for _, group := range agns.List() { 255 groups = append(groups, group.(string)) 256 } 257 258 p.SetAffinitygroupnames(groups) 259 } 260 261 // If there is a security_group_ids supplied, add it to the parameter struct 262 if sgids := d.Get("security_group_ids").(*schema.Set); sgids.Len() > 0 { 263 var groups []string 264 265 for _, group := range sgids.List() { 266 groups = append(groups, group.(string)) 267 } 268 269 p.SetSecuritygroupids(groups) 270 } 271 272 // If there is a security_group_names supplied, add it to the parameter struct 273 if sgns := d.Get("security_group_names").(*schema.Set); sgns.Len() > 0 { 274 var groups []string 275 276 for _, group := range sgns.List() { 277 groups = append(groups, group.(string)) 278 } 279 280 p.SetSecuritygroupnames(groups) 281 } 282 283 // If there is a project supplied, we retrieve and set the project id 284 if err := setProjectid(p, cs, d); err != nil { 285 return err 286 } 287 288 // If a keypair is supplied, add it to the parameter struct 289 if keypair, ok := d.GetOk("keypair"); ok { 290 p.SetKeypair(keypair.(string)) 291 } 292 293 // If the user data contains any info, it needs to be base64 encoded and 294 // added to the parameter struct 295 if userData, ok := d.GetOk("user_data"); ok { 296 ud := base64.StdEncoding.EncodeToString([]byte(userData.(string))) 297 298 // deployVirtualMachine uses POST by default, so max userdata is 32K 299 maxUD := 32768 300 301 if cs.HTTPGETOnly { 302 // deployVirtualMachine using GET instead, so max userdata is 2K 303 maxUD = 2048 304 } 305 306 if len(ud) > maxUD { 307 return fmt.Errorf( 308 "The supplied user_data contains %d bytes after encoding, "+ 309 "this exeeds the limit of %d bytes", len(ud), maxUD) 310 } 311 312 p.SetUserdata(ud) 313 } 314 315 // If there is a group supplied, add it to the parameter struct 316 if group, ok := d.GetOk("group"); ok { 317 p.SetGroup(group.(string)) 318 } 319 320 // If there is a root_disk_size supplied, add it to the parameter struct 321 if rootdisksize, ok := d.GetOk("root_disk_size"); ok { 322 p.SetRootdisksize(int64(rootdisksize.(int))) 323 } 324 325 // Create the new instance 326 r, err := cs.VirtualMachine.DeployVirtualMachine(p) 327 if err != nil { 328 return fmt.Errorf("Error creating the new instance %s: %s", name, err) 329 } 330 331 d.SetId(r.Id) 332 333 // Set the connection info for any configured provisioners 334 d.SetConnInfo(map[string]string{ 335 "host": r.Nic[0].Ipaddress, 336 "password": r.Password, 337 }) 338 339 return resourceCloudStackInstanceRead(d, meta) 340 } 341 342 func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) error { 343 cs := meta.(*cloudstack.CloudStackClient) 344 345 // Get the virtual machine details 346 vm, count, err := cs.VirtualMachine.GetVirtualMachineByID( 347 d.Id(), 348 cloudstack.WithProject(d.Get("project").(string)), 349 ) 350 if err != nil { 351 if count == 0 { 352 log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string)) 353 d.SetId("") 354 return nil 355 } 356 357 return err 358 } 359 360 // Update the config 361 d.Set("name", vm.Name) 362 d.Set("display_name", vm.Displayname) 363 d.Set("network_id", vm.Nic[0].Networkid) 364 d.Set("ip_address", vm.Nic[0].Ipaddress) 365 d.Set("group", vm.Group) 366 367 setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid) 368 setValueOrID(d, "template", vm.Templatename, vm.Templateid) 369 setValueOrID(d, "project", vm.Project, vm.Projectid) 370 setValueOrID(d, "zone", vm.Zonename, vm.Zoneid) 371 372 groups := &schema.Set{F: schema.HashString} 373 for _, group := range vm.Affinitygroup { 374 groups.Add(group.Id) 375 } 376 377 if groups.Len() > 0 { 378 d.Set("affinity_group_ids", groups) 379 } 380 381 agns := &schema.Set{F: schema.HashString} 382 for _, group := range vm.Affinitygroup { 383 agns.Add(group.Name) 384 } 385 386 if agns.Len() > 0 { 387 d.Set("affinity_group_names", agns) 388 } 389 390 sgids := &schema.Set{F: schema.HashString} 391 for _, group := range vm.Securitygroup { 392 sgids.Add(group.Id) 393 } 394 395 if sgids.Len() > 0 { 396 d.Set("security_group_ids", sgids) 397 } 398 399 sgns := &schema.Set{F: schema.HashString} 400 for _, group := range vm.Securitygroup { 401 sgns.Add(group.Name) 402 } 403 404 if sgns.Len() > 0 { 405 d.Set("security_group_names", sgns) 406 } 407 408 return nil 409 } 410 411 func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 412 cs := meta.(*cloudstack.CloudStackClient) 413 d.Partial(true) 414 415 name := d.Get("name").(string) 416 417 // Check if the display name is changed and if so, update the virtual machine 418 if d.HasChange("display_name") { 419 log.Printf("[DEBUG] Display name changed for %s, starting update", name) 420 421 // Create a new parameter struct 422 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 423 424 // Set the new display name 425 p.SetDisplayname(d.Get("display_name").(string)) 426 427 // Update the display name 428 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 429 if err != nil { 430 return fmt.Errorf( 431 "Error updating the display name for instance %s: %s", name, err) 432 } 433 434 d.SetPartial("display_name") 435 } 436 437 // Check if the group is changed and if so, update the virtual machine 438 if d.HasChange("group") { 439 log.Printf("[DEBUG] Group changed for %s, starting update", name) 440 441 // Create a new parameter struct 442 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 443 444 // Set the new group 445 p.SetGroup(d.Get("group").(string)) 446 447 // Update the display name 448 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 449 if err != nil { 450 return fmt.Errorf( 451 "Error updating the group for instance %s: %s", name, err) 452 } 453 454 d.SetPartial("group") 455 } 456 457 // Attributes that require reboot to update 458 if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") || d.HasChange("keypair") { 459 // Before we can actually make these changes, the virtual machine must be stopped 460 _, err := cs.VirtualMachine.StopVirtualMachine( 461 cs.VirtualMachine.NewStopVirtualMachineParams(d.Id())) 462 if err != nil { 463 return fmt.Errorf( 464 "Error stopping instance %s before making changes: %s", name, err) 465 } 466 467 // Check if the name has changed and if so, update the name 468 if d.HasChange("name") { 469 log.Printf("[DEBUG] Name for %s changed to %s, starting update", d.Id(), name) 470 471 // Create a new parameter struct 472 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 473 474 // Set the new name 475 p.SetName(name) 476 477 // Update the display name 478 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 479 if err != nil { 480 return fmt.Errorf( 481 "Error updating the name for instance %s: %s", name, err) 482 } 483 484 d.SetPartial("name") 485 } 486 487 // Check if the service offering is changed and if so, update the offering 488 if d.HasChange("service_offering") { 489 log.Printf("[DEBUG] Service offering changed for %s, starting update", name) 490 491 // Retrieve the service_offering ID 492 serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string)) 493 if e != nil { 494 return e.Error() 495 } 496 497 // Create a new parameter struct 498 p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid) 499 500 // Change the service offering 501 _, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p) 502 if err != nil { 503 return fmt.Errorf( 504 "Error changing the service offering for instance %s: %s", name, err) 505 } 506 d.SetPartial("service_offering") 507 } 508 509 if d.HasChange("affinity_group_ids") { 510 p := cs.AffinityGroup.NewUpdateVMAffinityGroupParams(d.Id()) 511 groups := []string{} 512 513 if ags := d.Get("affinity_group_ids").(*schema.Set); ags.Len() > 0 { 514 for _, group := range ags.List() { 515 groups = append(groups, group.(string)) 516 } 517 } 518 519 p.SetAffinitygroupids(groups) 520 } 521 522 if d.HasChange("keypair") { 523 log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name) 524 525 p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string)) 526 527 // Change the ssh keypair 528 _, err = cs.SSH.ResetSSHKeyForVirtualMachine(p) 529 if err != nil { 530 return fmt.Errorf( 531 "Error changing the SSH keypair for instance %s: %s", name, err) 532 } 533 d.SetPartial("keypair") 534 } 535 536 // Start the virtual machine again 537 _, err = cs.VirtualMachine.StartVirtualMachine( 538 cs.VirtualMachine.NewStartVirtualMachineParams(d.Id())) 539 if err != nil { 540 return fmt.Errorf( 541 "Error starting instance %s after making changes", name) 542 } 543 } 544 545 d.Partial(false) 546 return resourceCloudStackInstanceRead(d, meta) 547 } 548 549 func resourceCloudStackInstanceDelete(d *schema.ResourceData, meta interface{}) error { 550 cs := meta.(*cloudstack.CloudStackClient) 551 552 // Create a new parameter struct 553 p := cs.VirtualMachine.NewDestroyVirtualMachineParams(d.Id()) 554 555 if d.Get("expunge").(bool) { 556 p.SetExpunge(true) 557 } 558 559 log.Printf("[INFO] Destroying instance: %s", d.Get("name").(string)) 560 if _, err := cs.VirtualMachine.DestroyVirtualMachine(p); err != nil { 561 // This is a very poor way to be told the ID does no longer exist :( 562 if strings.Contains(err.Error(), fmt.Sprintf( 563 "Invalid parameter id value=%s due to incorrect long value format, "+ 564 "or entity does not exist", d.Id())) { 565 return nil 566 } 567 568 return fmt.Errorf("Error destroying instance: %s", err) 569 } 570 571 return nil 572 }