github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/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 "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.TypeString, 101 Optional: true, 102 }, 103 "bus": { 104 Type: schema.TypeString, 105 Optional: true, 106 }, 107 "name": { 108 Type: schema.TypeString, 109 Optional: true, 110 }, 111 }, 112 }, 113 }, 114 "nic": { 115 Type: schema.TypeSet, 116 Required: true, 117 Elem: &schema.Resource{ 118 Schema: map[string]*schema.Schema{ 119 "lan": { 120 Type: schema.TypeInt, 121 Required: true, 122 }, 123 "name": { 124 Type: schema.TypeString, 125 Optional: true, 126 }, 127 "dhcp": { 128 Type: schema.TypeBool, 129 Optional: true, 130 }, 131 132 "ip": { 133 Type: schema.TypeString, 134 Optional: true, 135 }, 136 "firewall_active": { 137 Type: schema.TypeBool, 138 Optional: true, 139 }, 140 "firewall": { 141 Type: schema.TypeSet, 142 Optional: true, 143 Elem: &schema.Resource{ 144 Schema: map[string]*schema.Schema{ 145 "name": { 146 Type: schema.TypeString, 147 Optional: true, 148 }, 149 150 "protocol": { 151 Type: schema.TypeString, 152 Required: true, 153 }, 154 "source_mac": { 155 Type: schema.TypeString, 156 Optional: true, 157 }, 158 "source_ip": { 159 Type: schema.TypeString, 160 Optional: true, 161 }, 162 "target_ip": { 163 Type: schema.TypeString, 164 Optional: true, 165 }, 166 "ip": { 167 Type: schema.TypeString, 168 Optional: true, 169 }, 170 "port_range_start": { 171 Type: schema.TypeInt, 172 Optional: true, 173 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 174 if v.(int) < 1 && v.(int) > 65534 { 175 errors = append(errors, fmt.Errorf("Port start range must be between 1 and 65534")) 176 } 177 return 178 }, 179 }, 180 181 "port_range_end": { 182 Type: schema.TypeInt, 183 Optional: true, 184 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 185 if v.(int) < 1 && v.(int) > 65534 { 186 errors = append(errors, fmt.Errorf("Port end range must be between 1 and 65534")) 187 } 188 return 189 }, 190 }, 191 "icmp_type": { 192 Type: schema.TypeString, 193 Optional: true, 194 }, 195 "icmp_code": { 196 Type: schema.TypeString, 197 Optional: true, 198 }, 199 }, 200 }, 201 }, 202 }, 203 }, 204 }, 205 }, 206 } 207 } 208 209 func resourceProfitBricksServerCreate(d *schema.ResourceData, meta interface{}) error { 210 config := meta.(*Config) 211 profitbricks.SetAuth(config.Username, config.Password) 212 213 request := profitbricks.Server{ 214 Properties: profitbricks.ServerProperties{ 215 Name: d.Get("name").(string), 216 Cores: d.Get("cores").(int), 217 Ram: d.Get("ram").(int), 218 }, 219 } 220 221 if v, ok := d.GetOk("availability_zone"); ok { 222 request.Properties.AvailabilityZone = v.(string) 223 } 224 225 if v, ok := d.GetOk("cpu_family"); ok { 226 if v.(string) != "" { 227 request.Properties.CpuFamily = v.(string) 228 } 229 } 230 if vRaw, ok := d.GetOk("volume"); ok { 231 232 volumeRaw := vRaw.(*schema.Set).List() 233 234 for _, raw := range volumeRaw { 235 rawMap := raw.(map[string]interface{}) 236 var imagePassword, sshkey_path string 237 var image, licenceType string 238 239 if rawMap["image_name"] != nil { 240 image = getImageId(d.Get("datacenter_id").(string), rawMap["image_name"].(string), rawMap["disk_type"].(string)) 241 if image == "" { 242 dc := profitbricks.GetDatacenter(d.Get("datacenter_id").(string)) 243 return fmt.Errorf("Image '%s' doesn't exist. in location %s", rawMap["image_name"], dc.Properties.Location) 244 245 } 246 } 247 if rawMap["licence_type"] != nil { 248 licenceType = rawMap["licence_type"].(string) 249 } 250 251 if rawMap["image_password"] != nil { 252 imagePassword = rawMap["image_password"].(string) 253 } 254 if rawMap["ssh_key_path"] != nil { 255 sshkey_path = rawMap["ssh_key_path"].(string) 256 } 257 if rawMap["image_name"] != nil { 258 if imagePassword == "" && sshkey_path == "" { 259 return fmt.Errorf("'image_password' and 'ssh_key_path' are not provided.") 260 } 261 } 262 var publicKey string 263 var err error 264 if sshkey_path != "" { 265 log.Println("[DEBUG] GETTING THE KEY") 266 _, publicKey, err = getSshKey(d, sshkey_path) 267 if err != nil { 268 return fmt.Errorf("Error fetching sshkeys (%s)", err) 269 } 270 d.Set("sshkey", publicKey) 271 } 272 273 if image == "" && licenceType == "" { 274 return fmt.Errorf("Either 'image', or 'licenceType' must be set.") 275 } 276 277 request.Entities = &profitbricks.ServerEntities{ 278 Volumes: &profitbricks.Volumes{ 279 Items: []profitbricks.Volume{ 280 profitbricks.Volume{ 281 Properties: profitbricks.VolumeProperties{ 282 Name: rawMap["name"].(string), 283 Size: rawMap["size"].(int), 284 Type: rawMap["disk_type"].(string), 285 ImagePassword: imagePassword, 286 Image: image, 287 Bus: rawMap["bus"].(string), 288 LicenceType: licenceType, 289 }, 290 }, 291 }, 292 }, 293 } 294 295 log.Printf("[DEBUG] PUBLIC KEY %s", publicKey) 296 297 if publicKey == "" { 298 request.Entities.Volumes.Items[0].Properties.SshKeys = nil 299 } else { 300 request.Entities.Volumes.Items[0].Properties.SshKeys = []string{publicKey} 301 } 302 } 303 304 } 305 306 if nRaw, ok := d.GetOk("nic"); ok { 307 nicRaw := nRaw.(*schema.Set).List() 308 309 for _, raw := range nicRaw { 310 rawMap := raw.(map[string]interface{}) 311 nic := profitbricks.Nic{Properties: profitbricks.NicProperties{}} 312 if rawMap["lan"] != nil { 313 nic.Properties.Lan = rawMap["lan"].(int) 314 } 315 if rawMap["name"] != nil { 316 nic.Properties.Name = rawMap["name"].(string) 317 } 318 if rawMap["dhcp"] != nil { 319 nic.Properties.Dhcp = rawMap["dhcp"].(bool) 320 } 321 if rawMap["firewall_active"] != nil { 322 nic.Properties.FirewallActive = rawMap["firewall_active"].(bool) 323 } 324 if rawMap["ip"] != nil { 325 rawIps := rawMap["ip"].(string) 326 ips := strings.Split(rawIps, ",") 327 if rawIps != "" { 328 nic.Properties.Ips = ips 329 } 330 } 331 request.Entities.Nics = &profitbricks.Nics{ 332 Items: []profitbricks.Nic{ 333 nic, 334 }, 335 } 336 337 if rawMap["firewall"] != nil { 338 rawFw := rawMap["firewall"].(*schema.Set).List() 339 for _, rraw := range rawFw { 340 fwRaw := rraw.(map[string]interface{}) 341 log.Println("[DEBUG] fwRaw", fwRaw["protocol"]) 342 343 firewall := profitbricks.FirewallRule{ 344 Properties: profitbricks.FirewallruleProperties{ 345 Protocol: fwRaw["protocol"].(string), 346 }, 347 } 348 349 if fwRaw["name"] != nil { 350 firewall.Properties.Name = fwRaw["name"].(string) 351 } 352 if fwRaw["source_mac"] != nil { 353 firewall.Properties.SourceMac = fwRaw["source_mac"].(string) 354 } 355 if fwRaw["source_ip"] != nil { 356 firewall.Properties.SourceIp = fwRaw["source_ip"].(string) 357 } 358 if fwRaw["target_ip"] != nil { 359 firewall.Properties.TargetIp = fwRaw["target_ip"].(string) 360 } 361 if fwRaw["port_range_start"] != nil { 362 firewall.Properties.PortRangeStart = fwRaw["port_range_start"].(int) 363 } 364 if fwRaw["port_range_end"] != nil { 365 firewall.Properties.PortRangeEnd = fwRaw["port_range_end"].(int) 366 } 367 if fwRaw["icmp_type"] != nil { 368 firewall.Properties.IcmpType = fwRaw["icmp_type"].(string) 369 } 370 if fwRaw["icmp_code"] != nil { 371 firewall.Properties.IcmpCode = fwRaw["icmp_code"].(string) 372 } 373 374 request.Entities.Nics.Items[0].Entities = &profitbricks.NicEntities{ 375 &profitbricks.FirewallRules{ 376 Items: []profitbricks.FirewallRule{ 377 firewall, 378 }, 379 }, 380 } 381 } 382 383 } 384 } 385 } 386 387 if len(request.Entities.Nics.Items[0].Properties.Ips) == 0 { 388 request.Entities.Nics.Items[0].Properties.Ips = nil 389 } 390 server := profitbricks.CreateServer(d.Get("datacenter_id").(string), request) 391 392 jsn, _ := json.Marshal(request) 393 log.Println("[DEBUG] Server request", string(jsn)) 394 log.Println("[DEBUG] Server response", server.Response) 395 396 if server.StatusCode > 299 { 397 return fmt.Errorf( 398 "Error creating server: (%s)", server.Response) 399 } 400 401 err := waitTillProvisioned(meta, server.Headers.Get("Location")) 402 if err != nil { 403 return err 404 } 405 d.SetId(server.Id) 406 server = profitbricks.GetServer(d.Get("datacenter_id").(string), server.Id) 407 d.Set("primary_nic", server.Entities.Nics.Items[0]) 408 if len(server.Entities.Nics.Items[0].Properties.Ips) > 0 { 409 d.SetConnInfo(map[string]string{ 410 "type": "ssh", 411 "host": server.Entities.Nics.Items[0].Properties.Ips[0], 412 "password": request.Entities.Volumes.Items[0].Properties.ImagePassword, 413 }) 414 } 415 return resourceProfitBricksServerRead(d, meta) 416 } 417 418 func resourceProfitBricksServerRead(d *schema.ResourceData, meta interface{}) error { 419 config := meta.(*Config) 420 profitbricks.SetAuth(config.Username, config.Password) 421 422 dcId := d.Get("datacenter_id").(string) 423 424 server := profitbricks.GetServer(dcId, d.Id()) 425 426 primarynic := "" 427 428 if server.Entities != nil && server.Entities.Nics != nil && len(server.Entities.Nics.Items) > 0 { 429 for _, n := range server.Entities.Nics.Items { 430 if n.Properties.Lan != 0 { 431 lan := profitbricks.GetLan(dcId, strconv.Itoa(n.Properties.Lan)) 432 if lan.StatusCode > 299 { 433 return fmt.Errorf("Error while fetching a lan %s", lan.Response) 434 } 435 if lan.Properties.Public.(interface{}) == true { 436 primarynic = n.Id 437 break 438 } 439 } 440 } 441 } 442 443 d.Set("name", server.Properties.Name) 444 d.Set("cores", server.Properties.Cores) 445 d.Set("ram", server.Properties.Ram) 446 d.Set("availability_zone", server.Properties.AvailabilityZone) 447 d.Set("primary_nic", primarynic) 448 449 if server.Properties.BootVolume != nil { 450 d.Set("boot_volume", server.Properties.BootVolume.Id) 451 } 452 if server.Properties.BootCdrom != nil { 453 d.Set("boot_cdrom", server.Properties.BootCdrom.Id) 454 } 455 return nil 456 } 457 458 func resourceProfitBricksServerUpdate(d *schema.ResourceData, meta interface{}) error { 459 config := meta.(*Config) 460 profitbricks.SetAuth(config.Username, config.Password) 461 462 dcId := d.Get("datacenter_id").(string) 463 464 request := profitbricks.ServerProperties{} 465 466 if d.HasChange("name") { 467 _, n := d.GetChange("name") 468 request.Name = n.(string) 469 } 470 if d.HasChange("cores") { 471 _, n := d.GetChange("cores") 472 request.Cores = n.(int) 473 } 474 if d.HasChange("ram") { 475 _, n := d.GetChange("ram") 476 request.Ram = n.(int) 477 } 478 if d.HasChange("availability_zone") { 479 _, n := d.GetChange("availability_zone") 480 request.AvailabilityZone = n.(string) 481 } 482 if d.HasChange("cpu_family") { 483 _, n := d.GetChange("cpu_family") 484 request.CpuFamily = n.(string) 485 } 486 server := profitbricks.PatchServer(dcId, d.Id(), request) 487 log.Println("[INFO] hlab hlab", request) 488 489 //Volume stuff 490 if d.HasChange("volume") { 491 volume := server.Entities.Volumes.Items[0] 492 _, new := d.GetChange("volume") 493 494 newVolume := new.(*schema.Set).List() 495 properties := profitbricks.VolumeProperties{} 496 497 for _, raw := range newVolume { 498 rawMap := raw.(map[string]interface{}) 499 if rawMap["name"] != nil { 500 properties.Name = rawMap["name"].(string) 501 } 502 if rawMap["size"] != nil { 503 properties.Size = rawMap["size"].(int) 504 } 505 if rawMap["bus"] != nil { 506 properties.Bus = rawMap["bus"].(string) 507 } 508 } 509 510 volume = profitbricks.PatchVolume(d.Get("datacenter_id").(string), server.Entities.Volumes.Items[0].Id, properties) 511 log.Println("[INFO] blah blah", properties) 512 513 if volume.StatusCode > 299 { 514 return fmt.Errorf("Error patching volume (%s) (%s)", d.Id(), volume.Response) 515 } 516 517 err := waitTillProvisioned(meta, volume.Headers.Get("Location")) 518 if err != nil { 519 return err 520 } 521 } 522 523 //Nic stuff 524 if d.HasChange("nic") { 525 nic := profitbricks.Nic{} 526 for _, n := range server.Entities.Nics.Items { 527 if n.Id == d.Get("primary_nic").(string) { 528 nic = n 529 break 530 } 531 } 532 _, new := d.GetChange("nic") 533 534 newNic := new.(*schema.Set).List() 535 properties := profitbricks.NicProperties{} 536 537 for _, raw := range newNic { 538 rawMap := raw.(map[string]interface{}) 539 if rawMap["name"] != nil { 540 properties.Name = rawMap["name"].(string) 541 } 542 if rawMap["ip"] != nil { 543 rawIps := rawMap["ip"].(string) 544 ips := strings.Split(rawIps, ",") 545 546 if rawIps != "" { 547 nic.Properties.Ips = ips 548 } 549 } 550 if rawMap["lan"] != nil { 551 properties.Lan = rawMap["lan"].(int) 552 } 553 if rawMap["dhcp"] != nil { 554 properties.Dhcp = rawMap["dhcp"].(bool) 555 } 556 } 557 558 nic = profitbricks.PatchNic(d.Get("datacenter_id").(string), server.Id, server.Entities.Nics.Items[0].Id, properties) 559 log.Println("[INFO] blah blah", properties) 560 561 if nic.StatusCode > 299 { 562 return fmt.Errorf( 563 "Error patching nic (%s)", nic.Response) 564 } 565 566 err := waitTillProvisioned(meta, nic.Headers.Get("Location")) 567 if err != nil { 568 return err 569 } 570 } 571 572 if server.StatusCode > 299 { 573 return fmt.Errorf( 574 "Error patching server (%s) (%s)", d.Id(), server.Response) 575 } 576 return resourceProfitBricksServerRead(d, meta) 577 } 578 579 func resourceProfitBricksServerDelete(d *schema.ResourceData, meta interface{}) error { 580 config := meta.(*Config) 581 profitbricks.SetAuth(config.Username, config.Password) 582 583 dcId := d.Get("datacenter_id").(string) 584 585 server := profitbricks.GetServer(dcId, d.Id()) 586 587 if server.Properties.BootVolume != nil { 588 resp := profitbricks.DeleteVolume(dcId, server.Properties.BootVolume.Id) 589 err := waitTillProvisioned(meta, resp.Headers.Get("Location")) 590 if err != nil { 591 return err 592 } 593 } 594 595 resp := profitbricks.DeleteServer(dcId, d.Id()) 596 if resp.StatusCode > 299 { 597 return fmt.Errorf("An error occured while deleting a server ID %s %s", d.Id(), string(resp.Body)) 598 599 } 600 err := waitTillProvisioned(meta, resp.Headers.Get("Location")) 601 if err != nil { 602 return err 603 } 604 d.SetId("") 605 return nil 606 } 607 608 func getSshKey(d *schema.ResourceData, path string) (privatekey string, publickey string, err error) { 609 pemBytes, err := ioutil.ReadFile(path) 610 611 if err != nil { 612 return "", "", err 613 } 614 615 block, _ := pem.Decode(pemBytes) 616 617 if block == nil { 618 return "", "", errors.New("File " + path + " contains nothing") 619 } 620 621 priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) 622 623 if err != nil { 624 return "", "", err 625 } 626 627 priv_blk := pem.Block{ 628 Type: "RSA PRIVATE KEY", 629 Headers: nil, 630 Bytes: x509.MarshalPKCS1PrivateKey(priv), 631 } 632 633 pub, err := ssh.NewPublicKey(&priv.PublicKey) 634 if err != nil { 635 return "", "", err 636 } 637 publickey = string(ssh.MarshalAuthorizedKey(pub)) 638 privatekey = string(pem.EncodeToMemory(&priv_blk)) 639 640 return privatekey, publickey, nil 641 }