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  }