github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/profitbricks/resource_profitbricks_server.go (about)

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