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