github.com/danrjohnson/terraform@v0.7.0-rc2.0.20160627135212-d0fc1fa086ff/builtin/providers/cloudstack/resource_cloudstack_instance.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"log"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/xanzy/go-cloudstack/cloudstack"
    14  )
    15  
    16  func resourceCloudStackInstance() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceCloudStackInstanceCreate,
    19  		Read:   resourceCloudStackInstanceRead,
    20  		Update: resourceCloudStackInstanceUpdate,
    21  		Delete: resourceCloudStackInstanceDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"name": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Optional: true,
    27  				Computed: true,
    28  			},
    29  
    30  			"display_name": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Optional: true,
    33  				Computed: true,
    34  			},
    35  
    36  			"service_offering": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Required: true,
    39  			},
    40  
    41  			"network_id": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  				Computed: true,
    45  				ForceNew: true,
    46  			},
    47  
    48  			"network": &schema.Schema{
    49  				Type:       schema.TypeString,
    50  				Optional:   true,
    51  				ForceNew:   true,
    52  				Deprecated: "Please use the `network_id` field instead",
    53  			},
    54  
    55  			"ip_address": &schema.Schema{
    56  				Type:     schema.TypeString,
    57  				Optional: true,
    58  				Computed: true,
    59  				ForceNew: true,
    60  			},
    61  
    62  			"ipaddress": &schema.Schema{
    63  				Type:       schema.TypeString,
    64  				Optional:   true,
    65  				Computed:   true,
    66  				ForceNew:   true,
    67  				Deprecated: "Please use the `ip_address` field instead",
    68  			},
    69  
    70  			"template": &schema.Schema{
    71  				Type:     schema.TypeString,
    72  				Required: true,
    73  				ForceNew: true,
    74  			},
    75  
    76  			"affinity_group_ids": &schema.Schema{
    77  				Type:          schema.TypeSet,
    78  				Optional:      true,
    79  				Elem:          &schema.Schema{Type: schema.TypeString},
    80  				Set:           schema.HashString,
    81  				ConflictsWith: []string{"affinity_group_names"},
    82  			},
    83  
    84  			"affinity_group_names": &schema.Schema{
    85  				Type:          schema.TypeSet,
    86  				Optional:      true,
    87  				Elem:          &schema.Schema{Type: schema.TypeString},
    88  				Set:           schema.HashString,
    89  				ConflictsWith: []string{"affinity_group_ids"},
    90  			},
    91  
    92  			"security_group_ids": &schema.Schema{
    93  				Type:          schema.TypeSet,
    94  				Optional:      true,
    95  				ForceNew:      true,
    96  				Elem:          &schema.Schema{Type: schema.TypeString},
    97  				Set:           schema.HashString,
    98  				ConflictsWith: []string{"security_group_names"},
    99  			},
   100  
   101  			"security_group_names": &schema.Schema{
   102  				Type:          schema.TypeSet,
   103  				Optional:      true,
   104  				ForceNew:      true,
   105  				Elem:          &schema.Schema{Type: schema.TypeString},
   106  				Set:           schema.HashString,
   107  				ConflictsWith: []string{"security_group_ids"},
   108  			},
   109  
   110  			"project": &schema.Schema{
   111  				Type:     schema.TypeString,
   112  				Optional: true,
   113  				ForceNew: true,
   114  			},
   115  
   116  			"zone": &schema.Schema{
   117  				Type:     schema.TypeString,
   118  				Required: true,
   119  				ForceNew: true,
   120  			},
   121  
   122  			"keypair": &schema.Schema{
   123  				Type:     schema.TypeString,
   124  				Optional: true,
   125  			},
   126  
   127  			"user_data": &schema.Schema{
   128  				Type:     schema.TypeString,
   129  				Optional: true,
   130  				ForceNew: true,
   131  				StateFunc: func(v interface{}) string {
   132  					switch v.(type) {
   133  					case string:
   134  						hash := sha1.Sum([]byte(v.(string)))
   135  						return hex.EncodeToString(hash[:])
   136  					default:
   137  						return ""
   138  					}
   139  				},
   140  			},
   141  
   142  			"expunge": &schema.Schema{
   143  				Type:     schema.TypeBool,
   144  				Optional: true,
   145  				Default:  false,
   146  			},
   147  
   148  			"group": &schema.Schema{
   149  				Type:     schema.TypeString,
   150  				Optional: true,
   151  				Computed: true,
   152  			},
   153  
   154  			"root_disk_size": &schema.Schema{
   155  				Type:     schema.TypeInt,
   156  				Optional: true,
   157  				ForceNew: true,
   158  			},
   159  		},
   160  	}
   161  }
   162  
   163  func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   164  	cs := meta.(*cloudstack.CloudStackClient)
   165  
   166  	// Retrieve the service_offering ID
   167  	serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
   168  	if e != nil {
   169  		return e.Error()
   170  	}
   171  
   172  	// Retrieve the zone ID
   173  	zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
   174  	if e != nil {
   175  		return e.Error()
   176  	}
   177  
   178  	// Retrieve the zone object
   179  	zone, _, err := cs.Zone.GetZoneByID(zoneid)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	// Retrieve the template ID
   185  	templateid, e := retrieveTemplateID(cs, zone.Id, d.Get("template").(string))
   186  	if e != nil {
   187  		return e.Error()
   188  	}
   189  
   190  	// Create a new parameter struct
   191  	p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)
   192  
   193  	// Set the name
   194  	name, hasName := d.GetOk("name")
   195  	if hasName {
   196  		p.SetName(name.(string))
   197  	}
   198  
   199  	// Set the display name
   200  	if displayname, ok := d.GetOk("display_name"); ok {
   201  		p.SetDisplayname(displayname.(string))
   202  	} else if hasName {
   203  		p.SetDisplayname(name.(string))
   204  	}
   205  
   206  	if zone.Networktype == "Advanced" {
   207  		network, ok := d.GetOk("network_id")
   208  		if !ok {
   209  			network, ok = d.GetOk("network")
   210  		}
   211  		if !ok {
   212  			return errors.New(
   213  				"Either `network_id` or [deprecated] `network` must be provided when using a zone with network type `advanced`.")
   214  		}
   215  
   216  		// Retrieve the network ID
   217  		networkid, e := retrieveID(
   218  			cs,
   219  			"network",
   220  			network.(string),
   221  			cloudstack.WithProject(d.Get("project").(string)),
   222  		)
   223  		if e != nil {
   224  			return e.Error()
   225  		}
   226  
   227  		// Set the default network ID
   228  		p.SetNetworkids([]string{networkid})
   229  	}
   230  
   231  	// If there is a ipaddres supplied, add it to the parameter struct
   232  	ipaddress, ok := d.GetOk("ip_address")
   233  	if !ok {
   234  		ipaddress, ok = d.GetOk("ipaddress")
   235  	}
   236  	if ok {
   237  		p.SetIpaddress(ipaddress.(string))
   238  	}
   239  
   240  	if ags := d.Get("affinity_group_ids").(*schema.Set); ags.Len() > 0 {
   241  		var groups []string
   242  
   243  		for _, group := range ags.List() {
   244  			groups = append(groups, group.(string))
   245  		}
   246  
   247  		p.SetAffinitygroupids(groups)
   248  	}
   249  
   250  	// If there is a affinity_group_names supplied, add it to the parameter struct
   251  	if agns := d.Get("affinity_group_names").(*schema.Set); agns.Len() > 0 {
   252  		var groups []string
   253  
   254  		for _, group := range agns.List() {
   255  			groups = append(groups, group.(string))
   256  		}
   257  
   258  		p.SetAffinitygroupnames(groups)
   259  	}
   260  
   261  	// If there is a security_group_ids supplied, add it to the parameter struct
   262  	if sgids := d.Get("security_group_ids").(*schema.Set); sgids.Len() > 0 {
   263  		var groups []string
   264  
   265  		for _, group := range sgids.List() {
   266  			groups = append(groups, group.(string))
   267  		}
   268  
   269  		p.SetSecuritygroupids(groups)
   270  	}
   271  
   272  	// If there is a security_group_names supplied, add it to the parameter struct
   273  	if sgns := d.Get("security_group_names").(*schema.Set); sgns.Len() > 0 {
   274  		var groups []string
   275  
   276  		for _, group := range sgns.List() {
   277  			groups = append(groups, group.(string))
   278  		}
   279  
   280  		p.SetSecuritygroupnames(groups)
   281  	}
   282  
   283  	// If there is a project supplied, we retrieve and set the project id
   284  	if err := setProjectid(p, cs, d); err != nil {
   285  		return err
   286  	}
   287  
   288  	// If a keypair is supplied, add it to the parameter struct
   289  	if keypair, ok := d.GetOk("keypair"); ok {
   290  		p.SetKeypair(keypair.(string))
   291  	}
   292  
   293  	// If the user data contains any info, it needs to be base64 encoded and
   294  	// added to the parameter struct
   295  	if userData, ok := d.GetOk("user_data"); ok {
   296  		ud := base64.StdEncoding.EncodeToString([]byte(userData.(string)))
   297  
   298  		// deployVirtualMachine uses POST by default, so max userdata is 32K
   299  		maxUD := 32768
   300  
   301  		if cs.HTTPGETOnly {
   302  			// deployVirtualMachine using GET instead, so max userdata is 2K
   303  			maxUD = 2048
   304  		}
   305  
   306  		if len(ud) > maxUD {
   307  			return fmt.Errorf(
   308  				"The supplied user_data contains %d bytes after encoding, "+
   309  					"this exeeds the limit of %d bytes", len(ud), maxUD)
   310  		}
   311  
   312  		p.SetUserdata(ud)
   313  	}
   314  
   315  	// If there is a group supplied, add it to the parameter struct
   316  	if group, ok := d.GetOk("group"); ok {
   317  		p.SetGroup(group.(string))
   318  	}
   319  
   320  	// If there is a root_disk_size supplied, add it to the parameter struct
   321  	if rootdisksize, ok := d.GetOk("root_disk_size"); ok {
   322  		p.SetRootdisksize(int64(rootdisksize.(int)))
   323  	}
   324  
   325  	// Create the new instance
   326  	r, err := cs.VirtualMachine.DeployVirtualMachine(p)
   327  	if err != nil {
   328  		return fmt.Errorf("Error creating the new instance %s: %s", name, err)
   329  	}
   330  
   331  	d.SetId(r.Id)
   332  
   333  	// Set the connection info for any configured provisioners
   334  	d.SetConnInfo(map[string]string{
   335  		"host":     r.Nic[0].Ipaddress,
   336  		"password": r.Password,
   337  	})
   338  
   339  	return resourceCloudStackInstanceRead(d, meta)
   340  }
   341  
   342  func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) error {
   343  	cs := meta.(*cloudstack.CloudStackClient)
   344  
   345  	// Get the virtual machine details
   346  	vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(
   347  		d.Id(),
   348  		cloudstack.WithProject(d.Get("project").(string)),
   349  	)
   350  	if err != nil {
   351  		if count == 0 {
   352  			log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string))
   353  			d.SetId("")
   354  			return nil
   355  		}
   356  
   357  		return err
   358  	}
   359  
   360  	// Update the config
   361  	d.Set("name", vm.Name)
   362  	d.Set("display_name", vm.Displayname)
   363  	d.Set("network_id", vm.Nic[0].Networkid)
   364  	d.Set("ip_address", vm.Nic[0].Ipaddress)
   365  	d.Set("group", vm.Group)
   366  
   367  	setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid)
   368  	setValueOrID(d, "template", vm.Templatename, vm.Templateid)
   369  	setValueOrID(d, "project", vm.Project, vm.Projectid)
   370  	setValueOrID(d, "zone", vm.Zonename, vm.Zoneid)
   371  
   372  	groups := &schema.Set{F: schema.HashString}
   373  	for _, group := range vm.Affinitygroup {
   374  		groups.Add(group.Id)
   375  	}
   376  
   377  	if groups.Len() > 0 {
   378  		d.Set("affinity_group_ids", groups)
   379  	}
   380  
   381  	agns := &schema.Set{F: schema.HashString}
   382  	for _, group := range vm.Affinitygroup {
   383  		agns.Add(group.Name)
   384  	}
   385  
   386  	if agns.Len() > 0 {
   387  		d.Set("affinity_group_names", agns)
   388  	}
   389  
   390  	sgids := &schema.Set{F: schema.HashString}
   391  	for _, group := range vm.Securitygroup {
   392  		sgids.Add(group.Id)
   393  	}
   394  
   395  	if sgids.Len() > 0 {
   396  		d.Set("security_group_ids", sgids)
   397  	}
   398  
   399  	sgns := &schema.Set{F: schema.HashString}
   400  	for _, group := range vm.Securitygroup {
   401  		sgns.Add(group.Name)
   402  	}
   403  
   404  	if sgns.Len() > 0 {
   405  		d.Set("security_group_names", sgns)
   406  	}
   407  
   408  	return nil
   409  }
   410  
   411  func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   412  	cs := meta.(*cloudstack.CloudStackClient)
   413  	d.Partial(true)
   414  
   415  	name := d.Get("name").(string)
   416  
   417  	// Check if the display name is changed and if so, update the virtual machine
   418  	if d.HasChange("display_name") {
   419  		log.Printf("[DEBUG] Display name changed for %s, starting update", name)
   420  
   421  		// Create a new parameter struct
   422  		p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
   423  
   424  		// Set the new display name
   425  		p.SetDisplayname(d.Get("display_name").(string))
   426  
   427  		// Update the display name
   428  		_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
   429  		if err != nil {
   430  			return fmt.Errorf(
   431  				"Error updating the display name for instance %s: %s", name, err)
   432  		}
   433  
   434  		d.SetPartial("display_name")
   435  	}
   436  
   437  	// Check if the group is changed and if so, update the virtual machine
   438  	if d.HasChange("group") {
   439  		log.Printf("[DEBUG] Group changed for %s, starting update", name)
   440  
   441  		// Create a new parameter struct
   442  		p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
   443  
   444  		// Set the new group
   445  		p.SetGroup(d.Get("group").(string))
   446  
   447  		// Update the display name
   448  		_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
   449  		if err != nil {
   450  			return fmt.Errorf(
   451  				"Error updating the group for instance %s: %s", name, err)
   452  		}
   453  
   454  		d.SetPartial("group")
   455  	}
   456  
   457  	// Attributes that require reboot to update
   458  	if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") || d.HasChange("keypair") {
   459  		// Before we can actually make these changes, the virtual machine must be stopped
   460  		_, err := cs.VirtualMachine.StopVirtualMachine(
   461  			cs.VirtualMachine.NewStopVirtualMachineParams(d.Id()))
   462  		if err != nil {
   463  			return fmt.Errorf(
   464  				"Error stopping instance %s before making changes: %s", name, err)
   465  		}
   466  
   467  		// Check if the name has changed and if so, update the name
   468  		if d.HasChange("name") {
   469  			log.Printf("[DEBUG] Name for %s changed to %s, starting update", d.Id(), name)
   470  
   471  			// Create a new parameter struct
   472  			p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
   473  
   474  			// Set the new name
   475  			p.SetName(name)
   476  
   477  			// Update the display name
   478  			_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
   479  			if err != nil {
   480  				return fmt.Errorf(
   481  					"Error updating the name for instance %s: %s", name, err)
   482  			}
   483  
   484  			d.SetPartial("name")
   485  		}
   486  
   487  		// Check if the service offering is changed and if so, update the offering
   488  		if d.HasChange("service_offering") {
   489  			log.Printf("[DEBUG] Service offering changed for %s, starting update", name)
   490  
   491  			// Retrieve the service_offering ID
   492  			serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
   493  			if e != nil {
   494  				return e.Error()
   495  			}
   496  
   497  			// Create a new parameter struct
   498  			p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid)
   499  
   500  			// Change the service offering
   501  			_, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p)
   502  			if err != nil {
   503  				return fmt.Errorf(
   504  					"Error changing the service offering for instance %s: %s", name, err)
   505  			}
   506  			d.SetPartial("service_offering")
   507  		}
   508  
   509  		if d.HasChange("affinity_group_ids") {
   510  			p := cs.AffinityGroup.NewUpdateVMAffinityGroupParams(d.Id())
   511  			groups := []string{}
   512  
   513  			if ags := d.Get("affinity_group_ids").(*schema.Set); ags.Len() > 0 {
   514  				for _, group := range ags.List() {
   515  					groups = append(groups, group.(string))
   516  				}
   517  			}
   518  
   519  			p.SetAffinitygroupids(groups)
   520  		}
   521  
   522  		if d.HasChange("keypair") {
   523  			log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name)
   524  
   525  			p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string))
   526  
   527  			// Change the ssh keypair
   528  			_, err = cs.SSH.ResetSSHKeyForVirtualMachine(p)
   529  			if err != nil {
   530  				return fmt.Errorf(
   531  					"Error changing the SSH keypair for instance %s: %s", name, err)
   532  			}
   533  			d.SetPartial("keypair")
   534  		}
   535  
   536  		// Start the virtual machine again
   537  		_, err = cs.VirtualMachine.StartVirtualMachine(
   538  			cs.VirtualMachine.NewStartVirtualMachineParams(d.Id()))
   539  		if err != nil {
   540  			return fmt.Errorf(
   541  				"Error starting instance %s after making changes", name)
   542  		}
   543  	}
   544  
   545  	d.Partial(false)
   546  	return resourceCloudStackInstanceRead(d, meta)
   547  }
   548  
   549  func resourceCloudStackInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   550  	cs := meta.(*cloudstack.CloudStackClient)
   551  
   552  	// Create a new parameter struct
   553  	p := cs.VirtualMachine.NewDestroyVirtualMachineParams(d.Id())
   554  
   555  	if d.Get("expunge").(bool) {
   556  		p.SetExpunge(true)
   557  	}
   558  
   559  	log.Printf("[INFO] Destroying instance: %s", d.Get("name").(string))
   560  	if _, err := cs.VirtualMachine.DestroyVirtualMachine(p); err != nil {
   561  		// This is a very poor way to be told the ID does no longer exist :(
   562  		if strings.Contains(err.Error(), fmt.Sprintf(
   563  			"Invalid parameter id value=%s due to incorrect long value format, "+
   564  				"or entity does not exist", d.Id())) {
   565  			return nil
   566  		}
   567  
   568  		return fmt.Errorf("Error destroying instance: %s", err)
   569  	}
   570  
   571  	return nil
   572  }