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