github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/cloudstack/resource_cloudstack_instance.go (about)

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