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  }