github.com/memsql/terraform@v0.7.0-rc2.0.20160706152241-21e2173e0a32/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 ForceNew: true, 109 }, 110 111 "zone": &schema.Schema{ 112 Type: schema.TypeString, 113 Required: true, 114 ForceNew: true, 115 }, 116 117 "keypair": &schema.Schema{ 118 Type: schema.TypeString, 119 Optional: true, 120 }, 121 122 "user_data": &schema.Schema{ 123 Type: schema.TypeString, 124 Optional: true, 125 ForceNew: 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 the user data contains any info, it needs to be base64 encoded and 256 // added to the parameter struct 257 if userData, ok := d.GetOk("user_data"); ok { 258 ud := base64.StdEncoding.EncodeToString([]byte(userData.(string))) 259 260 // deployVirtualMachine uses POST by default, so max userdata is 32K 261 maxUD := 32768 262 263 if cs.HTTPGETOnly { 264 // deployVirtualMachine using GET instead, so max userdata is 2K 265 maxUD = 2048 266 } 267 268 if len(ud) > maxUD { 269 return fmt.Errorf( 270 "The supplied user_data contains %d bytes after encoding, "+ 271 "this exeeds the limit of %d bytes", len(ud), maxUD) 272 } 273 274 p.SetUserdata(ud) 275 } 276 277 // Create the new instance 278 r, err := cs.VirtualMachine.DeployVirtualMachine(p) 279 if err != nil { 280 return fmt.Errorf("Error creating the new instance %s: %s", name, err) 281 } 282 283 d.SetId(r.Id) 284 285 // Set the connection info for any configured provisioners 286 d.SetConnInfo(map[string]string{ 287 "host": r.Nic[0].Ipaddress, 288 "password": r.Password, 289 }) 290 291 return resourceCloudStackInstanceRead(d, meta) 292 } 293 294 func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) error { 295 cs := meta.(*cloudstack.CloudStackClient) 296 297 // Get the virtual machine details 298 vm, count, err := cs.VirtualMachine.GetVirtualMachineByID( 299 d.Id(), 300 cloudstack.WithProject(d.Get("project").(string)), 301 ) 302 if err != nil { 303 if count == 0 { 304 log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string)) 305 d.SetId("") 306 return nil 307 } 308 309 return err 310 } 311 312 // Update the config 313 d.Set("name", vm.Name) 314 d.Set("display_name", vm.Displayname) 315 d.Set("network_id", vm.Nic[0].Networkid) 316 d.Set("ip_address", vm.Nic[0].Ipaddress) 317 d.Set("group", vm.Group) 318 319 if _, ok := d.GetOk("affinity_group_ids"); ok { 320 groups := &schema.Set{F: schema.HashString} 321 for _, group := range vm.Affinitygroup { 322 groups.Add(group.Id) 323 } 324 d.Set("affinity_group_ids", groups) 325 } 326 327 if _, ok := d.GetOk("affinity_group_names"); ok { 328 groups := &schema.Set{F: schema.HashString} 329 for _, group := range vm.Affinitygroup { 330 groups.Add(group.Name) 331 } 332 d.Set("affinity_group_names", groups) 333 } 334 335 if _, ok := d.GetOk("security_group_ids"); ok { 336 groups := &schema.Set{F: schema.HashString} 337 for _, group := range vm.Securitygroup { 338 groups.Add(group.Id) 339 } 340 d.Set("security_group_ids", groups) 341 } 342 343 if _, ok := d.GetOk("security_group_names"); ok { 344 groups := &schema.Set{F: schema.HashString} 345 for _, group := range vm.Securitygroup { 346 groups.Add(group.Name) 347 } 348 d.Set("security_group_names", groups) 349 } 350 351 setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid) 352 setValueOrID(d, "template", vm.Templatename, vm.Templateid) 353 setValueOrID(d, "project", vm.Project, vm.Projectid) 354 setValueOrID(d, "zone", vm.Zonename, vm.Zoneid) 355 356 return nil 357 } 358 359 func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 360 cs := meta.(*cloudstack.CloudStackClient) 361 d.Partial(true) 362 363 name := d.Get("name").(string) 364 365 // Check if the display name is changed and if so, update the virtual machine 366 if d.HasChange("display_name") { 367 log.Printf("[DEBUG] Display name changed for %s, starting update", name) 368 369 // Create a new parameter struct 370 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 371 372 // Set the new display name 373 p.SetDisplayname(d.Get("display_name").(string)) 374 375 // Update the display name 376 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 377 if err != nil { 378 return fmt.Errorf( 379 "Error updating the display name for instance %s: %s", name, err) 380 } 381 382 d.SetPartial("display_name") 383 } 384 385 // Check if the group is changed and if so, update the virtual machine 386 if d.HasChange("group") { 387 log.Printf("[DEBUG] Group changed for %s, starting update", name) 388 389 // Create a new parameter struct 390 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 391 392 // Set the new group 393 p.SetGroup(d.Get("group").(string)) 394 395 // Update the display name 396 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 397 if err != nil { 398 return fmt.Errorf( 399 "Error updating the group for instance %s: %s", name, err) 400 } 401 402 d.SetPartial("group") 403 } 404 405 // Attributes that require reboot to update 406 if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") || d.HasChange("affinity_group_names") || d.HasChange("keypair") { 407 // Before we can actually make these changes, the virtual machine must be stopped 408 _, err := cs.VirtualMachine.StopVirtualMachine( 409 cs.VirtualMachine.NewStopVirtualMachineParams(d.Id())) 410 if err != nil { 411 return fmt.Errorf( 412 "Error stopping instance %s before making changes: %s", name, err) 413 } 414 415 // Check if the name has changed and if so, update the name 416 if d.HasChange("name") { 417 log.Printf("[DEBUG] Name for %s changed to %s, starting update", d.Id(), name) 418 419 // Create a new parameter struct 420 p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) 421 422 // Set the new name 423 p.SetName(name) 424 425 // Update the display name 426 _, err := cs.VirtualMachine.UpdateVirtualMachine(p) 427 if err != nil { 428 return fmt.Errorf( 429 "Error updating the name for instance %s: %s", name, err) 430 } 431 432 d.SetPartial("name") 433 } 434 435 // Check if the service offering is changed and if so, update the offering 436 if d.HasChange("service_offering") { 437 log.Printf("[DEBUG] Service offering changed for %s, starting update", name) 438 439 // Retrieve the service_offering ID 440 serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string)) 441 if e != nil { 442 return e.Error() 443 } 444 445 // Create a new parameter struct 446 p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid) 447 448 // Change the service offering 449 _, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p) 450 if err != nil { 451 return fmt.Errorf( 452 "Error changing the service offering for instance %s: %s", name, err) 453 } 454 d.SetPartial("service_offering") 455 } 456 457 if d.HasChange("affinity_group_ids") { 458 p := cs.AffinityGroup.NewUpdateVMAffinityGroupParams(d.Id()) 459 groups := []string{} 460 461 if agIDs := d.Get("affinity_group_ids").(*schema.Set); agIDs.Len() > 0 { 462 for _, group := range agIDs.List() { 463 groups = append(groups, group.(string)) 464 } 465 } 466 467 p.SetAffinitygroupids(groups) 468 } 469 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 p.SetAffinitygroupids(groups) 481 } 482 483 if d.HasChange("keypair") { 484 log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name) 485 486 p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string)) 487 488 // Change the ssh keypair 489 _, err = cs.SSH.ResetSSHKeyForVirtualMachine(p) 490 if err != nil { 491 return fmt.Errorf( 492 "Error changing the SSH keypair for instance %s: %s", name, err) 493 } 494 d.SetPartial("keypair") 495 } 496 497 // Start the virtual machine again 498 _, err = cs.VirtualMachine.StartVirtualMachine( 499 cs.VirtualMachine.NewStartVirtualMachineParams(d.Id())) 500 if err != nil { 501 return fmt.Errorf( 502 "Error starting instance %s after making changes", name) 503 } 504 } 505 506 d.Partial(false) 507 return resourceCloudStackInstanceRead(d, meta) 508 } 509 510 func resourceCloudStackInstanceDelete(d *schema.ResourceData, meta interface{}) error { 511 cs := meta.(*cloudstack.CloudStackClient) 512 513 // Create a new parameter struct 514 p := cs.VirtualMachine.NewDestroyVirtualMachineParams(d.Id()) 515 516 if d.Get("expunge").(bool) { 517 p.SetExpunge(true) 518 } 519 520 log.Printf("[INFO] Destroying instance: %s", d.Get("name").(string)) 521 if _, err := cs.VirtualMachine.DestroyVirtualMachine(p); err != nil { 522 // This is a very poor way to be told the ID does no longer exist :( 523 if strings.Contains(err.Error(), fmt.Sprintf( 524 "Invalid parameter id value=%s due to incorrect long value format, "+ 525 "or entity does not exist", d.Id())) { 526 return nil 527 } 528 529 return fmt.Errorf("Error destroying instance: %s", err) 530 } 531 532 return nil 533 }