github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/builtin/providers/profitbricks/resource_profitbricks_server.go (about) 1 package profitbricks 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "github.com/hashicorp/terraform/helper/schema" 7 "github.com/profitbricks/profitbricks-sdk-go" 8 "golang.org/x/crypto/ssh" 9 "io/ioutil" 10 "log" 11 "strings" 12 ) 13 14 func resourceProfitBricksServer() *schema.Resource { 15 return &schema.Resource{ 16 Create: resourceProfitBricksServerCreate, 17 Read: resourceProfitBricksServerRead, 18 Update: resourceProfitBricksServerUpdate, 19 Delete: resourceProfitBricksServerDelete, 20 Schema: map[string]*schema.Schema{ 21 22 //Server parameters 23 "name": { 24 Type: schema.TypeString, 25 Required: true, 26 }, 27 "cores": { 28 Type: schema.TypeInt, 29 Required: true, 30 }, 31 "ram": { 32 Type: schema.TypeInt, 33 Required: true, 34 }, 35 "availability_zone": { 36 Type: schema.TypeString, 37 Optional: true, 38 }, 39 "licence_type": { 40 Type: schema.TypeString, 41 Optional: true, 42 }, 43 44 "boot_volume": { 45 Type: schema.TypeString, 46 Computed: true, 47 }, 48 49 "boot_cdrom": { 50 Type: schema.TypeString, 51 Computed: true, 52 }, 53 "cpu_family": { 54 Type: schema.TypeString, 55 Optional: true, 56 }, 57 "boot_image": { 58 Type: schema.TypeString, 59 Computed: true, 60 }, 61 "primary_nic": { 62 Type: schema.TypeString, 63 Computed: true, 64 }, 65 "primary_ip": { 66 Type: schema.TypeString, 67 Computed: true, 68 }, 69 "datacenter_id": { 70 Type: schema.TypeString, 71 Required: true, 72 }, 73 "volume": { 74 Type: schema.TypeSet, 75 Required: true, 76 Elem: &schema.Resource{ 77 Schema: map[string]*schema.Schema{ 78 "image_name": { 79 Type: schema.TypeString, 80 Required: true, 81 }, 82 "size": { 83 Type: schema.TypeInt, 84 Required: true, 85 }, 86 87 "disk_type": { 88 Type: schema.TypeString, 89 Required: true, 90 }, 91 "image_password": { 92 Type: schema.TypeString, 93 Optional: true, 94 }, 95 "licence_type": { 96 Type: schema.TypeString, 97 Optional: true, 98 }, 99 "ssh_key_path": { 100 Type: schema.TypeList, 101 Elem: &schema.Schema{Type: schema.TypeString}, 102 Optional: true, 103 }, 104 "bus": { 105 Type: schema.TypeString, 106 Optional: true, 107 }, 108 "name": { 109 Type: schema.TypeString, 110 Optional: true, 111 }, 112 "availability_zone": { 113 Type: schema.TypeString, 114 Optional: true, 115 }, 116 }, 117 }, 118 }, 119 "nic": { 120 Type: schema.TypeSet, 121 Required: true, 122 Elem: &schema.Resource{ 123 Schema: map[string]*schema.Schema{ 124 "lan": { 125 Type: schema.TypeInt, 126 Required: true, 127 }, 128 "name": { 129 Type: schema.TypeString, 130 Optional: true, 131 }, 132 "dhcp": { 133 Type: schema.TypeBool, 134 Optional: true, 135 }, 136 137 "ip": { 138 Type: schema.TypeString, 139 Optional: true, 140 }, 141 "ips": { 142 Type: schema.TypeList, 143 Elem: &schema.Schema{Type: schema.TypeString}, 144 Computed: true, 145 }, 146 "nat": { 147 Type: schema.TypeBool, 148 Optional: true, 149 }, 150 "firewall_active": { 151 Type: schema.TypeBool, 152 Optional: true, 153 }, 154 "firewall": { 155 Type: schema.TypeSet, 156 Optional: true, 157 Elem: &schema.Resource{ 158 Schema: map[string]*schema.Schema{ 159 "name": { 160 Type: schema.TypeString, 161 Optional: true, 162 }, 163 164 "protocol": { 165 Type: schema.TypeString, 166 Required: true, 167 }, 168 "source_mac": { 169 Type: schema.TypeString, 170 Optional: true, 171 }, 172 "source_ip": { 173 Type: schema.TypeString, 174 Optional: true, 175 }, 176 "target_ip": { 177 Type: schema.TypeString, 178 Optional: true, 179 }, 180 "ip": { 181 Type: schema.TypeString, 182 Optional: true, 183 }, 184 "ips": { 185 Type: schema.TypeList, 186 Elem: &schema.Schema{Type: schema.TypeString}, 187 Optional: true, 188 }, 189 "port_range_start": { 190 Type: schema.TypeInt, 191 Optional: true, 192 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 193 if v.(int) < 1 && v.(int) > 65534 { 194 errors = append(errors, fmt.Errorf("Port start range must be between 1 and 65534")) 195 } 196 return 197 }, 198 }, 199 200 "port_range_end": { 201 Type: schema.TypeInt, 202 Optional: true, 203 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 204 if v.(int) < 1 && v.(int) > 65534 { 205 errors = append(errors, fmt.Errorf("Port end range must be between 1 and 65534")) 206 } 207 return 208 }, 209 }, 210 "icmp_type": { 211 Type: schema.TypeString, 212 Optional: true, 213 }, 214 "icmp_code": { 215 Type: schema.TypeString, 216 Optional: true, 217 }, 218 }, 219 }, 220 }, 221 }, 222 }, 223 }, 224 }, 225 } 226 } 227 228 func resourceProfitBricksServerCreate(d *schema.ResourceData, meta interface{}) error { 229 request := profitbricks.Server{ 230 Properties: profitbricks.ServerProperties{ 231 Name: d.Get("name").(string), 232 Cores: d.Get("cores").(int), 233 Ram: d.Get("ram").(int), 234 }, 235 } 236 237 if v, ok := d.GetOk("availability_zone"); ok { 238 request.Properties.AvailabilityZone = v.(string) 239 } 240 241 if v, ok := d.GetOk("cpu_family"); ok { 242 if v.(string) != "" { 243 request.Properties.CpuFamily = v.(string) 244 } 245 } 246 if vRaw, ok := d.GetOk("volume"); ok { 247 248 volumeRaw := vRaw.(*schema.Set).List() 249 250 for _, raw := range volumeRaw { 251 rawMap := raw.(map[string]interface{}) 252 var imagePassword string 253 //Can be one file or a list of files 254 var sshkey_path []interface{} 255 var image, licenceType, availabilityZone string 256 257 if rawMap["image_password"] != nil { 258 imagePassword = rawMap["image_password"].(string) 259 } 260 if rawMap["ssh_key_path"] != nil { 261 sshkey_path = rawMap["ssh_key_path"].([]interface{}) 262 } 263 264 image_name := rawMap["image_name"].(string) 265 if !IsValidUUID(image_name) { 266 if imagePassword == "" && len(sshkey_path) == 0 { 267 return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") 268 } 269 image = getImageId(d.Get("datacenter_id").(string), image_name, rawMap["disk_type"].(string)) 270 } else { 271 img := profitbricks.GetImage(image_name) 272 if img.StatusCode > 299 { 273 return fmt.Errorf("Error fetching image: %s", img.Response) 274 } 275 if img.Properties.Public == true { 276 if imagePassword == "" && len(sshkey_path) == 0 { 277 return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") 278 } 279 image = image_name 280 } else { 281 image = image_name 282 } 283 } 284 285 if rawMap["licence_type"] != nil { 286 licenceType = rawMap["licence_type"].(string) 287 } 288 289 var publicKeys []string 290 if len(sshkey_path) != 0 { 291 for _, path := range sshkey_path { 292 log.Printf("[DEBUG] Reading file %s", path) 293 publicKey, err := readPublicKey(path.(string)) 294 if err != nil { 295 return fmt.Errorf("Error fetching sshkey from file (%s) %s", path, err.Error()) 296 } 297 publicKeys = append(publicKeys, publicKey) 298 } 299 } 300 if rawMap["availability_zone"] != nil { 301 availabilityZone = rawMap["availability_zone"].(string) 302 } 303 if image == "" && licenceType == "" { 304 return fmt.Errorf("Either 'image', or 'licenceType' must be set.") 305 } 306 307 request.Entities = &profitbricks.ServerEntities{ 308 Volumes: &profitbricks.Volumes{ 309 Items: []profitbricks.Volume{ 310 { 311 Properties: profitbricks.VolumeProperties{ 312 Name: rawMap["name"].(string), 313 Size: rawMap["size"].(int), 314 Type: rawMap["disk_type"].(string), 315 ImagePassword: imagePassword, 316 Image: image, 317 Bus: rawMap["bus"].(string), 318 LicenceType: licenceType, 319 AvailabilityZone: availabilityZone, 320 }, 321 }, 322 }, 323 }, 324 } 325 326 if len(publicKeys) == 0 { 327 request.Entities.Volumes.Items[0].Properties.SshKeys = nil 328 } else { 329 request.Entities.Volumes.Items[0].Properties.SshKeys = publicKeys 330 } 331 } 332 333 } 334 335 if nRaw, ok := d.GetOk("nic"); ok { 336 nicRaw := nRaw.(*schema.Set).List() 337 338 for _, raw := range nicRaw { 339 rawMap := raw.(map[string]interface{}) 340 nic := profitbricks.Nic{Properties: profitbricks.NicProperties{}} 341 if rawMap["lan"] != nil { 342 nic.Properties.Lan = rawMap["lan"].(int) 343 } 344 if rawMap["name"] != nil { 345 nic.Properties.Name = rawMap["name"].(string) 346 } 347 if rawMap["dhcp"] != nil { 348 nic.Properties.Dhcp = rawMap["dhcp"].(bool) 349 } 350 if rawMap["firewall_active"] != nil { 351 nic.Properties.FirewallActive = rawMap["firewall_active"].(bool) 352 } 353 if rawMap["ip"] != nil { 354 rawIps := rawMap["ip"].(string) 355 ips := strings.Split(rawIps, ",") 356 if rawIps != "" { 357 nic.Properties.Ips = ips 358 } 359 } 360 if rawMap["nat"] != nil { 361 nic.Properties.Nat = rawMap["nat"].(bool) 362 } 363 request.Entities.Nics = &profitbricks.Nics{ 364 Items: []profitbricks.Nic{ 365 nic, 366 }, 367 } 368 369 if rawMap["firewall"] != nil { 370 rawFw := rawMap["firewall"].(*schema.Set).List() 371 for _, rraw := range rawFw { 372 fwRaw := rraw.(map[string]interface{}) 373 log.Println("[DEBUG] fwRaw", fwRaw["protocol"]) 374 375 firewall := profitbricks.FirewallRule{ 376 Properties: profitbricks.FirewallruleProperties{ 377 Protocol: fwRaw["protocol"].(string), 378 }, 379 } 380 381 if fwRaw["name"] != nil { 382 firewall.Properties.Name = fwRaw["name"].(string) 383 } 384 if fwRaw["source_mac"] != nil { 385 firewall.Properties.SourceMac = fwRaw["source_mac"].(string) 386 } 387 if fwRaw["source_ip"] != nil { 388 firewall.Properties.SourceIp = fwRaw["source_ip"].(string) 389 } 390 if fwRaw["target_ip"] != nil { 391 firewall.Properties.TargetIp = fwRaw["target_ip"].(string) 392 } 393 if fwRaw["port_range_start"] != nil { 394 firewall.Properties.PortRangeStart = fwRaw["port_range_start"].(int) 395 } 396 if fwRaw["port_range_end"] != nil { 397 firewall.Properties.PortRangeEnd = fwRaw["port_range_end"].(int) 398 } 399 if fwRaw["icmp_type"] != nil { 400 firewall.Properties.IcmpType = fwRaw["icmp_type"].(string) 401 } 402 if fwRaw["icmp_code"] != nil { 403 firewall.Properties.IcmpCode = fwRaw["icmp_code"].(string) 404 } 405 406 request.Entities.Nics.Items[0].Entities = &profitbricks.NicEntities{ 407 Firewallrules: &profitbricks.FirewallRules{ 408 Items: []profitbricks.FirewallRule{ 409 firewall, 410 }, 411 }, 412 } 413 } 414 415 } 416 } 417 } 418 419 if len(request.Entities.Nics.Items[0].Properties.Ips) == 0 { 420 request.Entities.Nics.Items[0].Properties.Ips = nil 421 } 422 server := profitbricks.CreateServer(d.Get("datacenter_id").(string), request) 423 424 jsn, _ := json.Marshal(request) 425 log.Println("[DEBUG] Server request", string(jsn)) 426 log.Println("[DEBUG] Server response", server.Response) 427 428 if server.StatusCode > 299 { 429 return fmt.Errorf( 430 "Error creating server: (%s)", server.Response) 431 } 432 433 err := waitTillProvisioned(meta, server.Headers.Get("Location")) 434 if err != nil { 435 return err 436 } 437 d.SetId(server.Id) 438 server = profitbricks.GetServer(d.Get("datacenter_id").(string), server.Id) 439 440 d.Set("primary_nic", server.Entities.Nics.Items[0].Id) 441 if len(server.Entities.Nics.Items[0].Properties.Ips) > 0 { 442 d.SetConnInfo(map[string]string{ 443 "type": "ssh", 444 "host": server.Entities.Nics.Items[0].Properties.Ips[0], 445 "password": request.Entities.Volumes.Items[0].Properties.ImagePassword, 446 }) 447 } 448 return resourceProfitBricksServerRead(d, meta) 449 } 450 451 func resourceProfitBricksServerRead(d *schema.ResourceData, meta interface{}) error { 452 dcId := d.Get("datacenter_id").(string) 453 serverId := d.Id() 454 455 server := profitbricks.GetServer(dcId, serverId) 456 if server.StatusCode > 299 { 457 if server.StatusCode == 404 { 458 d.SetId("") 459 return nil 460 } 461 return fmt.Errorf("Error occured while fetching a server ID %s %s", d.Id(), server.Response) 462 } 463 d.Set("name", server.Properties.Name) 464 d.Set("cores", server.Properties.Cores) 465 d.Set("ram", server.Properties.Ram) 466 d.Set("availability_zone", server.Properties.AvailabilityZone) 467 468 if primarynic, ok := d.GetOk("primary_nic"); ok { 469 d.Set("primary_nic", primarynic.(string)) 470 471 nic := profitbricks.GetNic(dcId, serverId, primarynic.(string)) 472 473 if len(nic.Properties.Ips) > 0 { 474 d.Set("primary_ip", nic.Properties.Ips[0]) 475 } 476 477 if nRaw, ok := d.GetOk("nic"); ok { 478 log.Printf("[DEBUG] parsing nic") 479 480 nicRaw := nRaw.(*schema.Set).List() 481 482 for _, raw := range nicRaw { 483 484 rawMap := raw.(map[string]interface{}) 485 486 rawMap["lan"] = nic.Properties.Lan 487 rawMap["name"] = nic.Properties.Name 488 rawMap["dhcp"] = nic.Properties.Dhcp 489 rawMap["nat"] = nic.Properties.Nat 490 rawMap["firewall_active"] = nic.Properties.FirewallActive 491 rawMap["ips"] = nic.Properties.Ips 492 } 493 d.Set("nic", nicRaw) 494 } 495 } 496 497 if server.Properties.BootVolume != nil { 498 d.Set("boot_volume", server.Properties.BootVolume.Id) 499 } 500 if server.Properties.BootCdrom != nil { 501 d.Set("boot_cdrom", server.Properties.BootCdrom.Id) 502 } 503 return nil 504 } 505 506 func resourceProfitBricksServerUpdate(d *schema.ResourceData, meta interface{}) error { 507 dcId := d.Get("datacenter_id").(string) 508 509 request := profitbricks.ServerProperties{} 510 511 if d.HasChange("name") { 512 _, n := d.GetChange("name") 513 request.Name = n.(string) 514 } 515 if d.HasChange("cores") { 516 _, n := d.GetChange("cores") 517 request.Cores = n.(int) 518 } 519 if d.HasChange("ram") { 520 _, n := d.GetChange("ram") 521 request.Ram = n.(int) 522 } 523 if d.HasChange("availability_zone") { 524 _, n := d.GetChange("availability_zone") 525 request.AvailabilityZone = n.(string) 526 } 527 if d.HasChange("cpu_family") { 528 _, n := d.GetChange("cpu_family") 529 request.CpuFamily = n.(string) 530 } 531 server := profitbricks.PatchServer(dcId, d.Id(), request) 532 533 //Volume stuff 534 if d.HasChange("volume") { 535 volume := server.Entities.Volumes.Items[0] 536 _, new := d.GetChange("volume") 537 538 newVolume := new.(*schema.Set).List() 539 properties := profitbricks.VolumeProperties{} 540 541 for _, raw := range newVolume { 542 rawMap := raw.(map[string]interface{}) 543 if rawMap["name"] != nil { 544 properties.Name = rawMap["name"].(string) 545 } 546 if rawMap["size"] != nil { 547 properties.Size = rawMap["size"].(int) 548 } 549 if rawMap["bus"] != nil { 550 properties.Bus = rawMap["bus"].(string) 551 } 552 } 553 554 volume = profitbricks.PatchVolume(d.Get("datacenter_id").(string), server.Entities.Volumes.Items[0].Id, properties) 555 556 if volume.StatusCode > 299 { 557 return fmt.Errorf("Error patching volume (%s) (%s)", d.Id(), volume.Response) 558 } 559 560 err := waitTillProvisioned(meta, volume.Headers.Get("Location")) 561 if err != nil { 562 return err 563 } 564 } 565 566 //Nic stuff 567 if d.HasChange("nic") { 568 nic := profitbricks.Nic{} 569 for _, n := range server.Entities.Nics.Items { 570 if n.Id == d.Get("primary_nic").(string) { 571 nic = n 572 break 573 } 574 } 575 _, new := d.GetChange("nic") 576 577 newNic := new.(*schema.Set).List() 578 properties := profitbricks.NicProperties{} 579 580 for _, raw := range newNic { 581 rawMap := raw.(map[string]interface{}) 582 if rawMap["name"] != nil { 583 properties.Name = rawMap["name"].(string) 584 } 585 if rawMap["ip"] != nil { 586 rawIps := rawMap["ip"].(string) 587 ips := strings.Split(rawIps, ",") 588 589 if rawIps != "" { 590 nic.Properties.Ips = ips 591 } 592 } 593 if rawMap["lan"] != nil { 594 properties.Lan = rawMap["lan"].(int) 595 } 596 if rawMap["dhcp"] != nil { 597 properties.Dhcp = rawMap["dhcp"].(bool) 598 } 599 if rawMap["nat"] != nil { 600 properties.Nat = rawMap["nat"].(bool) 601 } 602 } 603 604 nic = profitbricks.PatchNic(d.Get("datacenter_id").(string), server.Id, server.Entities.Nics.Items[0].Id, properties) 605 606 if nic.StatusCode > 299 { 607 return fmt.Errorf( 608 "Error patching nic (%s)", nic.Response) 609 } 610 611 err := waitTillProvisioned(meta, nic.Headers.Get("Location")) 612 if err != nil { 613 return err 614 } 615 } 616 617 if server.StatusCode > 299 { 618 return fmt.Errorf( 619 "Error patching server (%s) (%s)", d.Id(), server.Response) 620 } 621 return resourceProfitBricksServerRead(d, meta) 622 } 623 624 func resourceProfitBricksServerDelete(d *schema.ResourceData, meta interface{}) error { 625 dcId := d.Get("datacenter_id").(string) 626 627 server := profitbricks.GetServer(dcId, d.Id()) 628 629 if server.Properties.BootVolume != nil { 630 resp := profitbricks.DeleteVolume(dcId, server.Properties.BootVolume.Id) 631 err := waitTillProvisioned(meta, resp.Headers.Get("Location")) 632 if err != nil { 633 return err 634 } 635 } 636 637 resp := profitbricks.DeleteServer(dcId, d.Id()) 638 if resp.StatusCode > 299 { 639 return fmt.Errorf("An error occured while deleting a server ID %s %s", d.Id(), string(resp.Body)) 640 641 } 642 err := waitTillProvisioned(meta, resp.Headers.Get("Location")) 643 if err != nil { 644 return err 645 } 646 d.SetId("") 647 return nil 648 } 649 650 //Reads public key from file and returns key string iff valid 651 func readPublicKey(path string) (key string, err error) { 652 bytes, err := ioutil.ReadFile(path) 653 if err != nil { 654 return "", err 655 } 656 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(bytes) 657 if err != nil { 658 return "", err 659 } 660 return string(ssh.MarshalAuthorizedKey(pubKey)[:]), nil 661 }