github.com/peterbale/terraform@v0.9.0-beta2.0.20170315142748-5723acd55547/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 !IsValidUUID(rawMap["image_name"].(string)) { 258 if rawMap["image_name"] != nil { 259 image = getImageId(d.Get("datacenter_id").(string), rawMap["image_name"].(string), rawMap["disk_type"].(string)) 260 if image == "" { 261 dc := profitbricks.GetDatacenter(d.Get("datacenter_id").(string)) 262 return fmt.Errorf("Image '%s' doesn't exist. in location %s", rawMap["image_name"], dc.Properties.Location) 263 264 } 265 } 266 } else { 267 image = rawMap["image_name"].(string) 268 } 269 270 if rawMap["licence_type"] != nil { 271 licenceType = rawMap["licence_type"].(string) 272 } 273 274 if rawMap["image_password"] != nil { 275 imagePassword = rawMap["image_password"].(string) 276 } 277 if rawMap["ssh_key_path"] != nil { 278 sshkey_path = rawMap["ssh_key_path"].([]interface{}) 279 } 280 if rawMap["image_name"] != nil { 281 if imagePassword == "" && len(sshkey_path) == 0 { 282 return fmt.Errorf("Either 'image_password' or 'ssh_key_path' must be provided.") 283 } 284 } 285 var publicKeys []string 286 if len(sshkey_path) != 0 { 287 for _, path := range sshkey_path { 288 log.Printf("[DEBUG] Reading file %s", path) 289 publicKey, err := readPublicKey(path.(string)) 290 if err != nil { 291 return fmt.Errorf("Error fetching sshkey from file (%s) %s", path, err.Error()) 292 } 293 publicKeys = append(publicKeys, publicKey) 294 } 295 } 296 if rawMap["availability_zone"] != nil { 297 availabilityZone = rawMap["availability_zone"].(string) 298 } 299 if image == "" && licenceType == "" { 300 return fmt.Errorf("Either 'image', or 'licenceType' must be set.") 301 } 302 303 request.Entities = &profitbricks.ServerEntities{ 304 Volumes: &profitbricks.Volumes{ 305 Items: []profitbricks.Volume{ 306 { 307 Properties: profitbricks.VolumeProperties{ 308 Name: rawMap["name"].(string), 309 Size: rawMap["size"].(int), 310 Type: rawMap["disk_type"].(string), 311 ImagePassword: imagePassword, 312 Image: image, 313 Bus: rawMap["bus"].(string), 314 LicenceType: licenceType, 315 AvailabilityZone: availabilityZone, 316 }, 317 }, 318 }, 319 }, 320 } 321 322 if len(publicKeys) == 0 { 323 request.Entities.Volumes.Items[0].Properties.SshKeys = nil 324 } else { 325 request.Entities.Volumes.Items[0].Properties.SshKeys = publicKeys 326 } 327 } 328 329 } 330 331 if nRaw, ok := d.GetOk("nic"); ok { 332 nicRaw := nRaw.(*schema.Set).List() 333 334 for _, raw := range nicRaw { 335 rawMap := raw.(map[string]interface{}) 336 nic := profitbricks.Nic{Properties: profitbricks.NicProperties{}} 337 if rawMap["lan"] != nil { 338 nic.Properties.Lan = rawMap["lan"].(int) 339 } 340 if rawMap["name"] != nil { 341 nic.Properties.Name = rawMap["name"].(string) 342 } 343 if rawMap["dhcp"] != nil { 344 nic.Properties.Dhcp = rawMap["dhcp"].(bool) 345 } 346 if rawMap["firewall_active"] != nil { 347 nic.Properties.FirewallActive = rawMap["firewall_active"].(bool) 348 } 349 if rawMap["ip"] != nil { 350 rawIps := rawMap["ip"].(string) 351 ips := strings.Split(rawIps, ",") 352 if rawIps != "" { 353 nic.Properties.Ips = ips 354 } 355 } 356 if rawMap["nat"] != nil { 357 nic.Properties.Nat = rawMap["nat"].(bool) 358 } 359 request.Entities.Nics = &profitbricks.Nics{ 360 Items: []profitbricks.Nic{ 361 nic, 362 }, 363 } 364 365 if rawMap["firewall"] != nil { 366 rawFw := rawMap["firewall"].(*schema.Set).List() 367 for _, rraw := range rawFw { 368 fwRaw := rraw.(map[string]interface{}) 369 log.Println("[DEBUG] fwRaw", fwRaw["protocol"]) 370 371 firewall := profitbricks.FirewallRule{ 372 Properties: profitbricks.FirewallruleProperties{ 373 Protocol: fwRaw["protocol"].(string), 374 }, 375 } 376 377 if fwRaw["name"] != nil { 378 firewall.Properties.Name = fwRaw["name"].(string) 379 } 380 if fwRaw["source_mac"] != nil { 381 firewall.Properties.SourceMac = fwRaw["source_mac"].(string) 382 } 383 if fwRaw["source_ip"] != nil { 384 firewall.Properties.SourceIp = fwRaw["source_ip"].(string) 385 } 386 if fwRaw["target_ip"] != nil { 387 firewall.Properties.TargetIp = fwRaw["target_ip"].(string) 388 } 389 if fwRaw["port_range_start"] != nil { 390 firewall.Properties.PortRangeStart = fwRaw["port_range_start"].(int) 391 } 392 if fwRaw["port_range_end"] != nil { 393 firewall.Properties.PortRangeEnd = fwRaw["port_range_end"].(int) 394 } 395 if fwRaw["icmp_type"] != nil { 396 firewall.Properties.IcmpType = fwRaw["icmp_type"].(string) 397 } 398 if fwRaw["icmp_code"] != nil { 399 firewall.Properties.IcmpCode = fwRaw["icmp_code"].(string) 400 } 401 402 request.Entities.Nics.Items[0].Entities = &profitbricks.NicEntities{ 403 Firewallrules: &profitbricks.FirewallRules{ 404 Items: []profitbricks.FirewallRule{ 405 firewall, 406 }, 407 }, 408 } 409 } 410 411 } 412 } 413 } 414 415 if len(request.Entities.Nics.Items[0].Properties.Ips) == 0 { 416 request.Entities.Nics.Items[0].Properties.Ips = nil 417 } 418 server := profitbricks.CreateServer(d.Get("datacenter_id").(string), request) 419 420 jsn, _ := json.Marshal(request) 421 log.Println("[DEBUG] Server request", string(jsn)) 422 log.Println("[DEBUG] Server response", server.Response) 423 424 if server.StatusCode > 299 { 425 return fmt.Errorf( 426 "Error creating server: (%s)", server.Response) 427 } 428 429 err := waitTillProvisioned(meta, server.Headers.Get("Location")) 430 if err != nil { 431 return err 432 } 433 d.SetId(server.Id) 434 server = profitbricks.GetServer(d.Get("datacenter_id").(string), server.Id) 435 436 d.Set("primary_nic", server.Entities.Nics.Items[0].Id) 437 if len(server.Entities.Nics.Items[0].Properties.Ips) > 0 { 438 d.SetConnInfo(map[string]string{ 439 "type": "ssh", 440 "host": server.Entities.Nics.Items[0].Properties.Ips[0], 441 "password": request.Entities.Volumes.Items[0].Properties.ImagePassword, 442 }) 443 } 444 return resourceProfitBricksServerRead(d, meta) 445 } 446 447 func resourceProfitBricksServerRead(d *schema.ResourceData, meta interface{}) error { 448 dcId := d.Get("datacenter_id").(string) 449 serverId := d.Id() 450 451 server := profitbricks.GetServer(dcId, serverId) 452 primarynic := d.Get("primary_nic").(string) 453 454 d.Set("name", server.Properties.Name) 455 d.Set("cores", server.Properties.Cores) 456 d.Set("ram", server.Properties.Ram) 457 d.Set("availability_zone", server.Properties.AvailabilityZone) 458 d.Set("primary_nic", primarynic) 459 460 nic := profitbricks.GetNic(dcId, serverId, primarynic) 461 462 if len(nic.Properties.Ips) > 0 { 463 d.Set("primary_ip", nic.Properties.Ips[0]) 464 } 465 466 if nRaw, ok := d.GetOk("nic"); ok { 467 log.Printf("[DEBUG] parsing nic") 468 469 nicRaw := nRaw.(*schema.Set).List() 470 471 for _, raw := range nicRaw { 472 473 rawMap := raw.(map[string]interface{}) 474 475 rawMap["lan"] = nic.Properties.Lan 476 rawMap["name"] = nic.Properties.Name 477 rawMap["dhcp"] = nic.Properties.Dhcp 478 rawMap["nat"] = nic.Properties.Nat 479 rawMap["firewall_active"] = nic.Properties.FirewallActive 480 rawMap["ips"] = nic.Properties.Ips 481 } 482 d.Set("nic", nicRaw) 483 } 484 485 if server.Properties.BootVolume != nil { 486 d.Set("boot_volume", server.Properties.BootVolume.Id) 487 } 488 if server.Properties.BootCdrom != nil { 489 d.Set("boot_cdrom", server.Properties.BootCdrom.Id) 490 } 491 return nil 492 } 493 494 func resourceProfitBricksServerUpdate(d *schema.ResourceData, meta interface{}) error { 495 dcId := d.Get("datacenter_id").(string) 496 497 request := profitbricks.ServerProperties{} 498 499 if d.HasChange("name") { 500 _, n := d.GetChange("name") 501 request.Name = n.(string) 502 } 503 if d.HasChange("cores") { 504 _, n := d.GetChange("cores") 505 request.Cores = n.(int) 506 } 507 if d.HasChange("ram") { 508 _, n := d.GetChange("ram") 509 request.Ram = n.(int) 510 } 511 if d.HasChange("availability_zone") { 512 _, n := d.GetChange("availability_zone") 513 request.AvailabilityZone = n.(string) 514 } 515 if d.HasChange("cpu_family") { 516 _, n := d.GetChange("cpu_family") 517 request.CpuFamily = n.(string) 518 } 519 server := profitbricks.PatchServer(dcId, d.Id(), request) 520 521 //Volume stuff 522 if d.HasChange("volume") { 523 volume := server.Entities.Volumes.Items[0] 524 _, new := d.GetChange("volume") 525 526 newVolume := new.(*schema.Set).List() 527 properties := profitbricks.VolumeProperties{} 528 529 for _, raw := range newVolume { 530 rawMap := raw.(map[string]interface{}) 531 if rawMap["name"] != nil { 532 properties.Name = rawMap["name"].(string) 533 } 534 if rawMap["size"] != nil { 535 properties.Size = rawMap["size"].(int) 536 } 537 if rawMap["bus"] != nil { 538 properties.Bus = rawMap["bus"].(string) 539 } 540 } 541 542 volume = profitbricks.PatchVolume(d.Get("datacenter_id").(string), server.Entities.Volumes.Items[0].Id, properties) 543 544 if volume.StatusCode > 299 { 545 return fmt.Errorf("Error patching volume (%s) (%s)", d.Id(), volume.Response) 546 } 547 548 err := waitTillProvisioned(meta, volume.Headers.Get("Location")) 549 if err != nil { 550 return err 551 } 552 } 553 554 //Nic stuff 555 if d.HasChange("nic") { 556 nic := profitbricks.Nic{} 557 for _, n := range server.Entities.Nics.Items { 558 if n.Id == d.Get("primary_nic").(string) { 559 nic = n 560 break 561 } 562 } 563 _, new := d.GetChange("nic") 564 565 newNic := new.(*schema.Set).List() 566 properties := profitbricks.NicProperties{} 567 568 for _, raw := range newNic { 569 rawMap := raw.(map[string]interface{}) 570 if rawMap["name"] != nil { 571 properties.Name = rawMap["name"].(string) 572 } 573 if rawMap["ip"] != nil { 574 rawIps := rawMap["ip"].(string) 575 ips := strings.Split(rawIps, ",") 576 577 if rawIps != "" { 578 nic.Properties.Ips = ips 579 } 580 } 581 if rawMap["lan"] != nil { 582 properties.Lan = rawMap["lan"].(int) 583 } 584 if rawMap["dhcp"] != nil { 585 properties.Dhcp = rawMap["dhcp"].(bool) 586 } 587 if rawMap["nat"] != nil { 588 properties.Nat = rawMap["nat"].(bool) 589 } 590 } 591 592 nic = profitbricks.PatchNic(d.Get("datacenter_id").(string), server.Id, server.Entities.Nics.Items[0].Id, properties) 593 594 if nic.StatusCode > 299 { 595 return fmt.Errorf( 596 "Error patching nic (%s)", nic.Response) 597 } 598 599 err := waitTillProvisioned(meta, nic.Headers.Get("Location")) 600 if err != nil { 601 return err 602 } 603 } 604 605 if server.StatusCode > 299 { 606 return fmt.Errorf( 607 "Error patching server (%s) (%s)", d.Id(), server.Response) 608 } 609 return resourceProfitBricksServerRead(d, meta) 610 } 611 612 func resourceProfitBricksServerDelete(d *schema.ResourceData, meta interface{}) error { 613 dcId := d.Get("datacenter_id").(string) 614 615 server := profitbricks.GetServer(dcId, d.Id()) 616 617 if server.Properties.BootVolume != nil { 618 resp := profitbricks.DeleteVolume(dcId, server.Properties.BootVolume.Id) 619 err := waitTillProvisioned(meta, resp.Headers.Get("Location")) 620 if err != nil { 621 return err 622 } 623 } 624 625 resp := profitbricks.DeleteServer(dcId, d.Id()) 626 if resp.StatusCode > 299 { 627 return fmt.Errorf("An error occured while deleting a server ID %s %s", d.Id(), string(resp.Body)) 628 629 } 630 err := waitTillProvisioned(meta, resp.Headers.Get("Location")) 631 if err != nil { 632 return err 633 } 634 d.SetId("") 635 return nil 636 } 637 638 //Reads public key from file and returns key string iff valid 639 func readPublicKey(path string) (key string, err error) { 640 bytes, err := ioutil.ReadFile(path) 641 if err != nil { 642 return "", err 643 } 644 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(bytes) 645 if err != nil { 646 return "", err 647 } 648 return string(ssh.MarshalAuthorizedKey(pubKey)[:]), nil 649 }