github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/oneandone/resource_oneandone_server.go (about) 1 package oneandone 2 3 import ( 4 "crypto/x509" 5 "encoding/pem" 6 "fmt" 7 "github.com/1and1/oneandone-cloudserver-sdk-go" 8 "github.com/hashicorp/terraform/helper/schema" 9 "golang.org/x/crypto/ssh" 10 "io/ioutil" 11 "log" 12 "strings" 13 14 "errors" 15 ) 16 17 func resourceOneandOneServer() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceOneandOneServerCreate, 20 Read: resourceOneandOneServerRead, 21 Update: resourceOneandOneServerUpdate, 22 Delete: resourceOneandOneServerDelete, 23 Schema: map[string]*schema.Schema{ 24 "name": { 25 Type: schema.TypeString, 26 Required: true, 27 }, 28 "description": { 29 Type: schema.TypeString, 30 Optional: true, 31 }, 32 "image": { 33 Type: schema.TypeString, 34 Required: true, 35 }, 36 "vcores": { 37 Type: schema.TypeInt, 38 Required: true, 39 }, 40 "cores_per_processor": { 41 Type: schema.TypeInt, 42 Required: true, 43 }, 44 "ram": { 45 Type: schema.TypeFloat, 46 Required: true, 47 }, 48 "ssh_key_path": { 49 Type: schema.TypeString, 50 Optional: true, 51 }, 52 "password": { 53 Type: schema.TypeString, 54 Optional: true, 55 Sensitive: true, 56 }, 57 "datacenter": { 58 Type: schema.TypeString, 59 Optional: true, 60 }, 61 "ip": { 62 Type: schema.TypeString, 63 Optional: true, 64 }, 65 "ips": { 66 Type: schema.TypeList, 67 Elem: &schema.Resource{ 68 Schema: map[string]*schema.Schema{ 69 "id": { 70 Type: schema.TypeString, 71 Computed: true, 72 }, 73 "ip": { 74 Type: schema.TypeString, 75 Computed: true, 76 }, 77 "firewall_policy_id": { 78 Type: schema.TypeString, 79 Optional: true, 80 }, 81 }, 82 }, 83 Computed: true, 84 }, 85 "hdds": { 86 Type: schema.TypeList, 87 Elem: &schema.Resource{ 88 Schema: map[string]*schema.Schema{ 89 "id": { 90 Type: schema.TypeString, 91 Computed: true, 92 }, 93 "disk_size": { 94 Type: schema.TypeInt, 95 Required: true, 96 }, 97 "is_main": { 98 Type: schema.TypeBool, 99 Optional: true, 100 }, 101 }, 102 }, 103 Required: true, 104 }, 105 "firewall_policy_id": { 106 Type: schema.TypeString, 107 Optional: true, 108 }, 109 "monitoring_policy_id": { 110 Type: schema.TypeString, 111 Optional: true, 112 }, 113 "loadbalancer_id": { 114 Type: schema.TypeString, 115 Optional: true, 116 }, 117 }, 118 } 119 } 120 121 func resourceOneandOneServerCreate(d *schema.ResourceData, meta interface{}) error { 122 config := meta.(*Config) 123 124 saps, _ := config.API.ListServerAppliances() 125 126 var sa oneandone.ServerAppliance 127 for _, a := range saps { 128 129 if a.Type == "IMAGE" && strings.Contains(strings.ToLower(a.Name), strings.ToLower(d.Get("image").(string))) { 130 sa = a 131 break 132 } 133 } 134 135 var hdds []oneandone.Hdd 136 if raw, ok := d.GetOk("hdds"); ok { 137 rawhdds := raw.([]interface{}) 138 139 var istheremain bool 140 for _, raw := range rawhdds { 141 hd := raw.(map[string]interface{}) 142 hdd := oneandone.Hdd{ 143 Size: hd["disk_size"].(int), 144 IsMain: hd["is_main"].(bool), 145 } 146 147 if hdd.IsMain { 148 if hdd.Size < sa.MinHddSize { 149 return fmt.Errorf(fmt.Sprintf("Minimum required disk size %d", sa.MinHddSize)) 150 } 151 istheremain = true 152 } 153 154 hdds = append(hdds, hdd) 155 } 156 157 if !istheremain { 158 return fmt.Errorf("At least one HDD has to be %s", "`is_main`") 159 } 160 } 161 162 req := oneandone.ServerRequest{ 163 Name: d.Get("name").(string), 164 Description: d.Get("description").(string), 165 ApplianceId: sa.Id, 166 PowerOn: true, 167 Hardware: oneandone.Hardware{ 168 Vcores: d.Get("vcores").(int), 169 CoresPerProcessor: d.Get("cores_per_processor").(int), 170 Ram: float32(d.Get("ram").(float64)), 171 Hdds: hdds, 172 }, 173 } 174 175 if raw, ok := d.GetOk("ip"); ok { 176 177 new_ip := raw.(string) 178 179 ips, err := config.API.ListPublicIps() 180 if err != nil { 181 return err 182 } 183 184 for _, ip := range ips { 185 if ip.IpAddress == new_ip { 186 req.IpId = ip.Id 187 break 188 } 189 } 190 191 log.Println("[DEBUG] req.IP", req.IpId) 192 } 193 194 if raw, ok := d.GetOk("datacenter"); ok { 195 196 dcs, err := config.API.ListDatacenters() 197 198 if err != nil { 199 return fmt.Errorf("An error occured while fetching list of datacenters %s", err) 200 201 } 202 203 decenter := raw.(string) 204 for _, dc := range dcs { 205 if strings.ToLower(dc.CountryCode) == strings.ToLower(decenter) { 206 req.DatacenterId = dc.Id 207 break 208 } 209 } 210 } 211 212 if fwp_id, ok := d.GetOk("firewall_policy_id"); ok { 213 req.FirewallPolicyId = fwp_id.(string) 214 } 215 216 if mp_id, ok := d.GetOk("monitoring_policy_id"); ok { 217 req.MonitoringPolicyId = mp_id.(string) 218 } 219 220 if mp_id, ok := d.GetOk("loadbalancer_id"); ok { 221 req.LoadBalancerId = mp_id.(string) 222 } 223 224 var privateKey string 225 if raw, ok := d.GetOk("ssh_key_path"); ok { 226 rawpath := raw.(string) 227 228 priv, publicKey, err := getSshKey(rawpath) 229 privateKey = priv 230 if err != nil { 231 return err 232 } 233 234 req.SSHKey = publicKey 235 } 236 237 var password string 238 if raw, ok := d.GetOk("password"); ok { 239 req.Password = raw.(string) 240 password = req.Password 241 } 242 243 server_id, server, err := config.API.CreateServer(&req) 244 if err != nil { 245 return err 246 } 247 248 err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries) 249 250 d.SetId(server_id) 251 server, err = config.API.GetServer(d.Id()) 252 if err != nil { 253 return err 254 } 255 256 if password == "" { 257 password = server.FirstPassword 258 } 259 d.SetConnInfo(map[string]string{ 260 "type": "ssh", 261 "host": server.Ips[0].Ip, 262 "password": password, 263 "private_key": privateKey, 264 }) 265 266 return resourceOneandOneServerRead(d, meta) 267 } 268 269 func resourceOneandOneServerRead(d *schema.ResourceData, meta interface{}) error { 270 config := meta.(*Config) 271 272 server, err := config.API.GetServer(d.Id()) 273 274 if err != nil { 275 if strings.Contains(err.Error(), "404") { 276 d.SetId("") 277 return nil 278 } 279 return err 280 } 281 282 d.Set("name", server.Name) 283 d.Set("datacenter", server.Datacenter.CountryCode) 284 285 d.Set("hdds", readHdds(server.Hardware)) 286 287 d.Set("ips", readIps(server.Ips)) 288 289 if len(server.FirstPassword) > 0 { 290 d.Set("password", server.FirstPassword) 291 } 292 293 return nil 294 } 295 296 func resourceOneandOneServerUpdate(d *schema.ResourceData, meta interface{}) error { 297 config := meta.(*Config) 298 299 if d.HasChange("name") || d.HasChange("description") { 300 _, name := d.GetChange("name") 301 _, description := d.GetChange("description") 302 server, err := config.API.RenameServer(d.Id(), name.(string), description.(string)) 303 if err != nil { 304 return err 305 } 306 307 err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries) 308 309 } 310 311 if d.HasChange("hdds") { 312 oldV, newV := d.GetChange("hdds") 313 newValues := newV.([]interface{}) 314 oldValues := oldV.([]interface{}) 315 316 if len(oldValues) > len(newValues) { 317 diff := difference(oldValues, newValues) 318 for _, old := range diff { 319 o := old.(map[string]interface{}) 320 old_id := o["id"].(string) 321 server, err := config.API.DeleteServerHdd(d.Id(), old_id) 322 if err != nil { 323 return err 324 } 325 326 err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries) 327 if err != nil { 328 return err 329 } 330 } 331 } else { 332 for _, newHdd := range newValues { 333 n := newHdd.(map[string]interface{}) 334 //old := oldHdd.(map[string]interface{}) 335 336 if n["id"].(string) == "" { 337 hdds := oneandone.ServerHdds{ 338 Hdds: []oneandone.Hdd{ 339 { 340 Size: n["disk_size"].(int), 341 IsMain: n["is_main"].(bool), 342 }, 343 }, 344 } 345 346 server, err := config.API.AddServerHdds(d.Id(), &hdds) 347 348 if err != nil { 349 return err 350 } 351 err = config.API.WaitForState(server, "POWERED_ON", 10, config.Retries) 352 if err != nil { 353 return err 354 } 355 } else { 356 id := n["id"].(string) 357 isMain := n["is_main"].(bool) 358 359 if id != "" && !isMain { 360 log.Println("[DEBUG] Resizing existing HDD") 361 config.API.ResizeServerHdd(d.Id(), id, n["disk_size"].(int)) 362 } 363 } 364 365 } 366 } 367 } 368 369 if d.HasChange("monitoring_policy_id") { 370 o, n := d.GetChange("monitoring_policy_id") 371 372 if n == nil { 373 mp, err := config.API.RemoveMonitoringPolicyServer(o.(string), d.Id()) 374 375 if err != nil { 376 return err 377 } 378 379 err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries) 380 if err != nil { 381 return err 382 } 383 } else { 384 mp, err := config.API.AttachMonitoringPolicyServers(n.(string), []string{d.Id()}) 385 if err != nil { 386 return err 387 } 388 389 err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries) 390 if err != nil { 391 return err 392 } 393 } 394 } 395 396 if d.HasChange("loadbalancer_id") { 397 o, n := d.GetChange("loadbalancer_id") 398 server, err := config.API.GetServer(d.Id()) 399 if err != nil { 400 return err 401 } 402 403 if n == nil || n.(string) == "" { 404 log.Println("[DEBUG] Removing") 405 log.Println("[DEBUG] IPS:", server.Ips) 406 407 for _, ip := range server.Ips { 408 mp, err := config.API.DeleteLoadBalancerServerIp(o.(string), ip.Id) 409 410 if err != nil { 411 return err 412 } 413 414 err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries) 415 if err != nil { 416 return err 417 } 418 } 419 } else { 420 log.Println("[DEBUG] Adding") 421 ip_ids := []string{} 422 for _, ip := range server.Ips { 423 ip_ids = append(ip_ids, ip.Id) 424 } 425 mp, err := config.API.AddLoadBalancerServerIps(n.(string), ip_ids) 426 if err != nil { 427 return err 428 } 429 430 err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries) 431 if err != nil { 432 return err 433 } 434 435 } 436 } 437 438 if d.HasChange("firewall_policy_id") { 439 server, err := config.API.GetServer(d.Id()) 440 if err != nil { 441 return err 442 } 443 444 o, n := d.GetChange("firewall_policy_id") 445 if n == nil { 446 for _, ip := range server.Ips { 447 mp, err := config.API.DeleteFirewallPolicyServerIp(o.(string), ip.Id) 448 if err != nil { 449 return err 450 } 451 452 err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries) 453 if err != nil { 454 return err 455 } 456 } 457 } else { 458 ip_ids := []string{} 459 for _, ip := range server.Ips { 460 ip_ids = append(ip_ids, ip.Id) 461 } 462 463 mp, err := config.API.AddFirewallPolicyServerIps(n.(string), ip_ids) 464 if err != nil { 465 return err 466 } 467 468 err = config.API.WaitForState(mp, "ACTIVE", 30, config.Retries) 469 if err != nil { 470 return err 471 } 472 } 473 } 474 475 return resourceOneandOneServerRead(d, meta) 476 } 477 478 func resourceOneandOneServerDelete(d *schema.ResourceData, meta interface{}) error { 479 config := meta.(*Config) 480 481 _, ok := d.GetOk("ip") 482 483 server, err := config.API.DeleteServer(d.Id(), ok) 484 if err != nil { 485 return err 486 } 487 488 err = config.API.WaitUntilDeleted(server) 489 490 if err != nil { 491 log.Println("[DEBUG] ************ ERROR While waiting ************") 492 return err 493 } 494 return nil 495 } 496 497 func readHdds(hardware *oneandone.Hardware) []map[string]interface{} { 498 hdds := make([]map[string]interface{}, 0, len(hardware.Hdds)) 499 500 for _, hd := range hardware.Hdds { 501 hdds = append(hdds, map[string]interface{}{ 502 "id": hd.Id, 503 "disk_size": hd.Size, 504 "is_main": hd.IsMain, 505 }) 506 } 507 508 return hdds 509 } 510 511 func readIps(ips []oneandone.ServerIp) []map[string]interface{} { 512 raw := make([]map[string]interface{}, 0, len(ips)) 513 for _, ip := range ips { 514 515 toadd := map[string]interface{}{ 516 "ip": ip.Ip, 517 "id": ip.Id, 518 } 519 520 if ip.Firewall != nil { 521 toadd["firewall_policy_id"] = ip.Firewall.Id 522 } 523 raw = append(raw, toadd) 524 } 525 526 return raw 527 } 528 529 func getSshKey(path string) (privatekey string, publickey string, err error) { 530 pemBytes, err := ioutil.ReadFile(path) 531 532 if err != nil { 533 return "", "", err 534 } 535 536 block, _ := pem.Decode(pemBytes) 537 538 if block == nil { 539 return "", "", errors.New("File " + path + " contains nothing") 540 } 541 542 priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) 543 544 if err != nil { 545 return "", "", err 546 } 547 548 priv_blk := pem.Block{ 549 Type: "RSA PRIVATE KEY", 550 Headers: nil, 551 Bytes: x509.MarshalPKCS1PrivateKey(priv), 552 } 553 554 pub, err := ssh.NewPublicKey(&priv.PublicKey) 555 if err != nil { 556 return "", "", err 557 } 558 publickey = string(ssh.MarshalAuthorizedKey(pub)) 559 privatekey = string(pem.EncodeToMemory(&priv_blk)) 560 561 return privatekey, publickey, nil 562 }