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