github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/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 rawMap["image_password"] != nil {
   258  				imagePassword = rawMap["image_password"].(string)
   259  			}
   260  			if rawMap["ssh_key_path"] != nil {
   261  				sshkey_path = rawMap["ssh_key_path"].([]interface{})
   262  			}
   263  
   264  			image_name := rawMap["image_name"].(string)
   265  			if !IsValidUUID(image_name) {
   266  				if imagePassword == "" && len(sshkey_path) == 0 {
   267  					return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
   268  				}
   269  				image = getImageId(d.Get("datacenter_id").(string), image_name, rawMap["disk_type"].(string))
   270  			} else {
   271  				img := profitbricks.GetImage(image_name)
   272  				if img.StatusCode > 299 {
   273  					return fmt.Errorf("Error fetching image: %s", img.Response)
   274  				}
   275  				if img.Properties.Public == true {
   276  					if imagePassword == "" && len(sshkey_path) == 0 {
   277  						return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
   278  					}
   279  					image = image_name
   280  				} else {
   281  					image = image_name
   282  				}
   283  			}
   284  
   285  			if rawMap["licence_type"] != nil {
   286  				licenceType = rawMap["licence_type"].(string)
   287  			}
   288  
   289  			var publicKeys []string
   290  			if len(sshkey_path) != 0 {
   291  				for _, path := range sshkey_path {
   292  					log.Printf("[DEBUG] Reading file %s", path)
   293  					publicKey, err := readPublicKey(path.(string))
   294  					if err != nil {
   295  						return fmt.Errorf("Error fetching sshkey from file (%s) %s", path, err.Error())
   296  					}
   297  					publicKeys = append(publicKeys, publicKey)
   298  				}
   299  			}
   300  			if rawMap["availability_zone"] != nil {
   301  				availabilityZone = rawMap["availability_zone"].(string)
   302  			}
   303  			if image == "" && licenceType == "" {
   304  				return fmt.Errorf("Either 'image', or 'licenceType' must be set.")
   305  			}
   306  
   307  			request.Entities = &profitbricks.ServerEntities{
   308  				Volumes: &profitbricks.Volumes{
   309  					Items: []profitbricks.Volume{
   310  						{
   311  							Properties: profitbricks.VolumeProperties{
   312  								Name:             rawMap["name"].(string),
   313  								Size:             rawMap["size"].(int),
   314  								Type:             rawMap["disk_type"].(string),
   315  								ImagePassword:    imagePassword,
   316  								Image:            image,
   317  								Bus:              rawMap["bus"].(string),
   318  								LicenceType:      licenceType,
   319  								AvailabilityZone: availabilityZone,
   320  							},
   321  						},
   322  					},
   323  				},
   324  			}
   325  
   326  			if len(publicKeys) == 0 {
   327  				request.Entities.Volumes.Items[0].Properties.SshKeys = nil
   328  			} else {
   329  				request.Entities.Volumes.Items[0].Properties.SshKeys = publicKeys
   330  			}
   331  		}
   332  
   333  	}
   334  
   335  	if nRaw, ok := d.GetOk("nic"); ok {
   336  		nicRaw := nRaw.(*schema.Set).List()
   337  
   338  		for _, raw := range nicRaw {
   339  			rawMap := raw.(map[string]interface{})
   340  			nic := profitbricks.Nic{Properties: profitbricks.NicProperties{}}
   341  			if rawMap["lan"] != nil {
   342  				nic.Properties.Lan = rawMap["lan"].(int)
   343  			}
   344  			if rawMap["name"] != nil {
   345  				nic.Properties.Name = rawMap["name"].(string)
   346  			}
   347  			if rawMap["dhcp"] != nil {
   348  				nic.Properties.Dhcp = rawMap["dhcp"].(bool)
   349  			}
   350  			if rawMap["firewall_active"] != nil {
   351  				nic.Properties.FirewallActive = rawMap["firewall_active"].(bool)
   352  			}
   353  			if rawMap["ip"] != nil {
   354  				rawIps := rawMap["ip"].(string)
   355  				ips := strings.Split(rawIps, ",")
   356  				if rawIps != "" {
   357  					nic.Properties.Ips = ips
   358  				}
   359  			}
   360  			if rawMap["nat"] != nil {
   361  				nic.Properties.Nat = rawMap["nat"].(bool)
   362  			}
   363  			request.Entities.Nics = &profitbricks.Nics{
   364  				Items: []profitbricks.Nic{
   365  					nic,
   366  				},
   367  			}
   368  
   369  			if rawMap["firewall"] != nil {
   370  				rawFw := rawMap["firewall"].(*schema.Set).List()
   371  				for _, rraw := range rawFw {
   372  					fwRaw := rraw.(map[string]interface{})
   373  					log.Println("[DEBUG] fwRaw", fwRaw["protocol"])
   374  
   375  					firewall := profitbricks.FirewallRule{
   376  						Properties: profitbricks.FirewallruleProperties{
   377  							Protocol: fwRaw["protocol"].(string),
   378  						},
   379  					}
   380  
   381  					if fwRaw["name"] != nil {
   382  						firewall.Properties.Name = fwRaw["name"].(string)
   383  					}
   384  					if fwRaw["source_mac"] != nil {
   385  						firewall.Properties.SourceMac = fwRaw["source_mac"].(string)
   386  					}
   387  					if fwRaw["source_ip"] != nil {
   388  						firewall.Properties.SourceIp = fwRaw["source_ip"].(string)
   389  					}
   390  					if fwRaw["target_ip"] != nil {
   391  						firewall.Properties.TargetIp = fwRaw["target_ip"].(string)
   392  					}
   393  					if fwRaw["port_range_start"] != nil {
   394  						firewall.Properties.PortRangeStart = fwRaw["port_range_start"].(int)
   395  					}
   396  					if fwRaw["port_range_end"] != nil {
   397  						firewall.Properties.PortRangeEnd = fwRaw["port_range_end"].(int)
   398  					}
   399  					if fwRaw["icmp_type"] != nil {
   400  						firewall.Properties.IcmpType = fwRaw["icmp_type"].(string)
   401  					}
   402  					if fwRaw["icmp_code"] != nil {
   403  						firewall.Properties.IcmpCode = fwRaw["icmp_code"].(string)
   404  					}
   405  
   406  					request.Entities.Nics.Items[0].Entities = &profitbricks.NicEntities{
   407  						Firewallrules: &profitbricks.FirewallRules{
   408  							Items: []profitbricks.FirewallRule{
   409  								firewall,
   410  							},
   411  						},
   412  					}
   413  				}
   414  
   415  			}
   416  		}
   417  	}
   418  
   419  	if len(request.Entities.Nics.Items[0].Properties.Ips) == 0 {
   420  		request.Entities.Nics.Items[0].Properties.Ips = nil
   421  	}
   422  	server := profitbricks.CreateServer(d.Get("datacenter_id").(string), request)
   423  
   424  	jsn, _ := json.Marshal(request)
   425  	log.Println("[DEBUG] Server request", string(jsn))
   426  	log.Println("[DEBUG] Server response", server.Response)
   427  
   428  	if server.StatusCode > 299 {
   429  		return fmt.Errorf(
   430  			"Error creating server: (%s)", server.Response)
   431  	}
   432  
   433  	err := waitTillProvisioned(meta, server.Headers.Get("Location"))
   434  	if err != nil {
   435  		return err
   436  	}
   437  	d.SetId(server.Id)
   438  	server = profitbricks.GetServer(d.Get("datacenter_id").(string), server.Id)
   439  
   440  	d.Set("primary_nic", server.Entities.Nics.Items[0].Id)
   441  	if len(server.Entities.Nics.Items[0].Properties.Ips) > 0 {
   442  		d.SetConnInfo(map[string]string{
   443  			"type":     "ssh",
   444  			"host":     server.Entities.Nics.Items[0].Properties.Ips[0],
   445  			"password": request.Entities.Volumes.Items[0].Properties.ImagePassword,
   446  		})
   447  	}
   448  	return resourceProfitBricksServerRead(d, meta)
   449  }
   450  
   451  func resourceProfitBricksServerRead(d *schema.ResourceData, meta interface{}) error {
   452  	dcId := d.Get("datacenter_id").(string)
   453  	serverId := d.Id()
   454  
   455  	server := profitbricks.GetServer(dcId, serverId)
   456  	if server.StatusCode > 299 {
   457  		if server.StatusCode == 404 {
   458  			d.SetId("")
   459  			return nil
   460  		}
   461  		return fmt.Errorf("Error occured while fetching a server ID %s %s", d.Id(), server.Response)
   462  	}
   463  	d.Set("name", server.Properties.Name)
   464  	d.Set("cores", server.Properties.Cores)
   465  	d.Set("ram", server.Properties.Ram)
   466  	d.Set("availability_zone", server.Properties.AvailabilityZone)
   467  
   468  	if primarynic, ok := d.GetOk("primary_nic"); ok {
   469  		d.Set("primary_nic", primarynic.(string))
   470  
   471  		nic := profitbricks.GetNic(dcId, serverId, primarynic.(string))
   472  
   473  		if len(nic.Properties.Ips) > 0 {
   474  			d.Set("primary_ip", nic.Properties.Ips[0])
   475  		}
   476  
   477  		if nRaw, ok := d.GetOk("nic"); ok {
   478  			log.Printf("[DEBUG] parsing nic")
   479  
   480  			nicRaw := nRaw.(*schema.Set).List()
   481  
   482  			for _, raw := range nicRaw {
   483  
   484  				rawMap := raw.(map[string]interface{})
   485  
   486  				rawMap["lan"] = nic.Properties.Lan
   487  				rawMap["name"] = nic.Properties.Name
   488  				rawMap["dhcp"] = nic.Properties.Dhcp
   489  				rawMap["nat"] = nic.Properties.Nat
   490  				rawMap["firewall_active"] = nic.Properties.FirewallActive
   491  				rawMap["ips"] = nic.Properties.Ips
   492  			}
   493  			d.Set("nic", nicRaw)
   494  		}
   495  	}
   496  
   497  	if server.Properties.BootVolume != nil {
   498  		d.Set("boot_volume", server.Properties.BootVolume.Id)
   499  	}
   500  	if server.Properties.BootCdrom != nil {
   501  		d.Set("boot_cdrom", server.Properties.BootCdrom.Id)
   502  	}
   503  	return nil
   504  }
   505  
   506  func resourceProfitBricksServerUpdate(d *schema.ResourceData, meta interface{}) error {
   507  	dcId := d.Get("datacenter_id").(string)
   508  
   509  	request := profitbricks.ServerProperties{}
   510  
   511  	if d.HasChange("name") {
   512  		_, n := d.GetChange("name")
   513  		request.Name = n.(string)
   514  	}
   515  	if d.HasChange("cores") {
   516  		_, n := d.GetChange("cores")
   517  		request.Cores = n.(int)
   518  	}
   519  	if d.HasChange("ram") {
   520  		_, n := d.GetChange("ram")
   521  		request.Ram = n.(int)
   522  	}
   523  	if d.HasChange("availability_zone") {
   524  		_, n := d.GetChange("availability_zone")
   525  		request.AvailabilityZone = n.(string)
   526  	}
   527  	if d.HasChange("cpu_family") {
   528  		_, n := d.GetChange("cpu_family")
   529  		request.CpuFamily = n.(string)
   530  	}
   531  	server := profitbricks.PatchServer(dcId, d.Id(), request)
   532  
   533  	//Volume stuff
   534  	if d.HasChange("volume") {
   535  		volume := server.Entities.Volumes.Items[0]
   536  		_, new := d.GetChange("volume")
   537  
   538  		newVolume := new.(*schema.Set).List()
   539  		properties := profitbricks.VolumeProperties{}
   540  
   541  		for _, raw := range newVolume {
   542  			rawMap := raw.(map[string]interface{})
   543  			if rawMap["name"] != nil {
   544  				properties.Name = rawMap["name"].(string)
   545  			}
   546  			if rawMap["size"] != nil {
   547  				properties.Size = rawMap["size"].(int)
   548  			}
   549  			if rawMap["bus"] != nil {
   550  				properties.Bus = rawMap["bus"].(string)
   551  			}
   552  		}
   553  
   554  		volume = profitbricks.PatchVolume(d.Get("datacenter_id").(string), server.Entities.Volumes.Items[0].Id, properties)
   555  
   556  		if volume.StatusCode > 299 {
   557  			return fmt.Errorf("Error patching volume (%s) (%s)", d.Id(), volume.Response)
   558  		}
   559  
   560  		err := waitTillProvisioned(meta, volume.Headers.Get("Location"))
   561  		if err != nil {
   562  			return err
   563  		}
   564  	}
   565  
   566  	//Nic stuff
   567  	if d.HasChange("nic") {
   568  		nic := profitbricks.Nic{}
   569  		for _, n := range server.Entities.Nics.Items {
   570  			if n.Id == d.Get("primary_nic").(string) {
   571  				nic = n
   572  				break
   573  			}
   574  		}
   575  		_, new := d.GetChange("nic")
   576  
   577  		newNic := new.(*schema.Set).List()
   578  		properties := profitbricks.NicProperties{}
   579  
   580  		for _, raw := range newNic {
   581  			rawMap := raw.(map[string]interface{})
   582  			if rawMap["name"] != nil {
   583  				properties.Name = rawMap["name"].(string)
   584  			}
   585  			if rawMap["ip"] != nil {
   586  				rawIps := rawMap["ip"].(string)
   587  				ips := strings.Split(rawIps, ",")
   588  
   589  				if rawIps != "" {
   590  					nic.Properties.Ips = ips
   591  				}
   592  			}
   593  			if rawMap["lan"] != nil {
   594  				properties.Lan = rawMap["lan"].(int)
   595  			}
   596  			if rawMap["dhcp"] != nil {
   597  				properties.Dhcp = rawMap["dhcp"].(bool)
   598  			}
   599  			if rawMap["nat"] != nil {
   600  				properties.Nat = rawMap["nat"].(bool)
   601  			}
   602  		}
   603  
   604  		nic = profitbricks.PatchNic(d.Get("datacenter_id").(string), server.Id, server.Entities.Nics.Items[0].Id, properties)
   605  
   606  		if nic.StatusCode > 299 {
   607  			return fmt.Errorf(
   608  				"Error patching nic (%s)", nic.Response)
   609  		}
   610  
   611  		err := waitTillProvisioned(meta, nic.Headers.Get("Location"))
   612  		if err != nil {
   613  			return err
   614  		}
   615  	}
   616  
   617  	if server.StatusCode > 299 {
   618  		return fmt.Errorf(
   619  			"Error patching server (%s) (%s)", d.Id(), server.Response)
   620  	}
   621  	return resourceProfitBricksServerRead(d, meta)
   622  }
   623  
   624  func resourceProfitBricksServerDelete(d *schema.ResourceData, meta interface{}) error {
   625  	dcId := d.Get("datacenter_id").(string)
   626  
   627  	server := profitbricks.GetServer(dcId, d.Id())
   628  
   629  	if server.Properties.BootVolume != nil {
   630  		resp := profitbricks.DeleteVolume(dcId, server.Properties.BootVolume.Id)
   631  		err := waitTillProvisioned(meta, resp.Headers.Get("Location"))
   632  		if err != nil {
   633  			return err
   634  		}
   635  	}
   636  
   637  	resp := profitbricks.DeleteServer(dcId, d.Id())
   638  	if resp.StatusCode > 299 {
   639  		return fmt.Errorf("An error occured while deleting a server ID %s %s", d.Id(), string(resp.Body))
   640  
   641  	}
   642  	err := waitTillProvisioned(meta, resp.Headers.Get("Location"))
   643  	if err != nil {
   644  		return err
   645  	}
   646  	d.SetId("")
   647  	return nil
   648  }
   649  
   650  //Reads public key from file and returns key string iff valid
   651  func readPublicKey(path string) (key string, err error) {
   652  	bytes, err := ioutil.ReadFile(path)
   653  	if err != nil {
   654  		return "", err
   655  	}
   656  	pubKey, _, _, _, err := ssh.ParseAuthorizedKey(bytes)
   657  	if err != nil {
   658  		return "", err
   659  	}
   660  	return string(ssh.MarshalAuthorizedKey(pubKey)[:]), nil
   661  }