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