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