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