github.com/serbaut/terraform@v0.6.12-0.20160607213102-ac2d195cc560/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  				Computed: true,
    80  				Elem:     &schema.Schema{Type: schema.TypeString},
    81  				Set:      schema.HashString,
    82  			},
    83  
    84  			"project": &schema.Schema{
    85  				Type:     schema.TypeString,
    86  				Optional: true,
    87  				ForceNew: true,
    88  			},
    89  
    90  			"zone": &schema.Schema{
    91  				Type:     schema.TypeString,
    92  				Required: true,
    93  				ForceNew: true,
    94  			},
    95  
    96  			"keypair": &schema.Schema{
    97  				Type:     schema.TypeString,
    98  				Optional: true,
    99  			},
   100  
   101  			"user_data": &schema.Schema{
   102  				Type:     schema.TypeString,
   103  				Optional: true,
   104  				ForceNew: true,
   105  				StateFunc: func(v interface{}) string {
   106  					switch v.(type) {
   107  					case string:
   108  						hash := sha1.Sum([]byte(v.(string)))
   109  						return hex.EncodeToString(hash[:])
   110  					default:
   111  						return ""
   112  					}
   113  				},
   114  			},
   115  
   116  			"expunge": &schema.Schema{
   117  				Type:     schema.TypeBool,
   118  				Optional: true,
   119  				Default:  false,
   120  			},
   121  
   122  			"group": &schema.Schema{
   123  				Type:     schema.TypeString,
   124  				Optional: true,
   125  				Computed: true,
   126  			},
   127  		},
   128  	}
   129  }
   130  
   131  func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   132  	cs := meta.(*cloudstack.CloudStackClient)
   133  
   134  	// Retrieve the service_offering ID
   135  	serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
   136  	if e != nil {
   137  		return e.Error()
   138  	}
   139  
   140  	// Retrieve the zone ID
   141  	zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
   142  	if e != nil {
   143  		return e.Error()
   144  	}
   145  
   146  	// Retrieve the zone object
   147  	zone, _, err := cs.Zone.GetZoneByID(zoneid)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	// Retrieve the template ID
   153  	templateid, e := retrieveTemplateID(cs, zone.Id, d.Get("template").(string))
   154  	if e != nil {
   155  		return e.Error()
   156  	}
   157  
   158  	// Create a new parameter struct
   159  	p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)
   160  
   161  	// Set the name
   162  	name, hasName := d.GetOk("name")
   163  	if hasName {
   164  		p.SetName(name.(string))
   165  	}
   166  
   167  	// Set the display name
   168  	if displayname, ok := d.GetOk("display_name"); ok {
   169  		p.SetDisplayname(displayname.(string))
   170  	} else if hasName {
   171  		p.SetDisplayname(name.(string))
   172  	}
   173  
   174  	if zone.Networktype == "Advanced" {
   175  		network, ok := d.GetOk("network_id")
   176  		if !ok {
   177  			network, ok = d.GetOk("network")
   178  		}
   179  		if !ok {
   180  			return errors.New(
   181  				"Either `network_id` or [deprecated] `network` must be provided when using a zone with network type `advanced`.")
   182  		}
   183  
   184  		// Retrieve the network ID
   185  		networkid, e := retrieveID(
   186  			cs,
   187  			"network",
   188  			network.(string),
   189  			cloudstack.WithProject(d.Get("project").(string)),
   190  		)
   191  		if e != nil {
   192  			return e.Error()
   193  		}
   194  
   195  		// Set the default network ID
   196  		p.SetNetworkids([]string{networkid})
   197  	}
   198  
   199  	// If there is a ipaddres supplied, add it to the parameter struct
   200  	ipaddress, ok := d.GetOk("ip_address")
   201  	if !ok {
   202  		ipaddress, ok = d.GetOk("ipaddress")
   203  	}
   204  	if ok {
   205  		p.SetIpaddress(ipaddress.(string))
   206  	}
   207  
   208  	if ags := d.Get("affinity_group_ids").(*schema.Set); ags.Len() > 0 {
   209  		var groups []string
   210  
   211  		for _, group := range ags.List() {
   212  			groups = append(groups, group.(string))
   213  		}
   214  
   215  		p.SetAffinitygroupids(groups)
   216  	}
   217  
   218  	// If there is a project supplied, we retrieve and set the project id
   219  	if err := setProjectid(p, cs, d); err != nil {
   220  		return err
   221  	}
   222  
   223  	// If a keypair is supplied, add it to the parameter struct
   224  	if keypair, ok := d.GetOk("keypair"); ok {
   225  		p.SetKeypair(keypair.(string))
   226  	}
   227  
   228  	// If the user data contains any info, it needs to be base64 encoded and
   229  	// added to the parameter struct
   230  	if userData, ok := d.GetOk("user_data"); ok {
   231  		ud := base64.StdEncoding.EncodeToString([]byte(userData.(string)))
   232  
   233  		// deployVirtualMachine uses POST by default, so max userdata is 32K
   234  		maxUD := 32768
   235  
   236  		if cs.HTTPGETOnly {
   237  			// deployVirtualMachine using GET instead, so max userdata is 2K
   238  			maxUD = 2048
   239  		}
   240  
   241  		if len(ud) > maxUD {
   242  			return fmt.Errorf(
   243  				"The supplied user_data contains %d bytes after encoding, "+
   244  					"this exeeds the limit of %d bytes", len(ud), maxUD)
   245  		}
   246  
   247  		p.SetUserdata(ud)
   248  	}
   249  
   250  	// If there is a group supplied, add it to the parameter struct
   251  	if group, ok := d.GetOk("group"); ok {
   252  		p.SetGroup(group.(string))
   253  	}
   254  
   255  	// Create the new instance
   256  	r, err := cs.VirtualMachine.DeployVirtualMachine(p)
   257  	if err != nil {
   258  		return fmt.Errorf("Error creating the new instance %s: %s", name, err)
   259  	}
   260  
   261  	d.SetId(r.Id)
   262  
   263  	// Set the connection info for any configured provisioners
   264  	d.SetConnInfo(map[string]string{
   265  		"host":     r.Nic[0].Ipaddress,
   266  		"password": r.Password,
   267  	})
   268  
   269  	return resourceCloudStackInstanceRead(d, meta)
   270  }
   271  
   272  func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) error {
   273  	cs := meta.(*cloudstack.CloudStackClient)
   274  
   275  	// Get the virtual machine details
   276  	vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(
   277  		d.Id(),
   278  		cloudstack.WithProject(d.Get("project").(string)),
   279  	)
   280  	if err != nil {
   281  		if count == 0 {
   282  			log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string))
   283  			d.SetId("")
   284  			return nil
   285  		}
   286  
   287  		return err
   288  	}
   289  
   290  	// Update the config
   291  	d.Set("name", vm.Name)
   292  	d.Set("display_name", vm.Displayname)
   293  	d.Set("network_id", vm.Nic[0].Networkid)
   294  	d.Set("ip_address", vm.Nic[0].Ipaddress)
   295  	d.Set("group", vm.Group)
   296  
   297  	setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid)
   298  	setValueOrID(d, "template", vm.Templatename, vm.Templateid)
   299  	setValueOrID(d, "project", vm.Project, vm.Projectid)
   300  	setValueOrID(d, "zone", vm.Zonename, vm.Zoneid)
   301  
   302  	groups := &schema.Set{F: schema.HashString}
   303  	for _, group := range vm.Affinitygroup {
   304  		groups.Add(group.Id)
   305  	}
   306  
   307  	if groups.Len() > 0 {
   308  		d.Set("affinity_group_ids", groups)
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   315  	cs := meta.(*cloudstack.CloudStackClient)
   316  	d.Partial(true)
   317  
   318  	name := d.Get("name").(string)
   319  
   320  	// Check if the display name is changed and if so, update the virtual machine
   321  	if d.HasChange("display_name") {
   322  		log.Printf("[DEBUG] Display name changed for %s, starting update", name)
   323  
   324  		// Create a new parameter struct
   325  		p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
   326  
   327  		// Set the new display name
   328  		p.SetDisplayname(d.Get("display_name").(string))
   329  
   330  		// Update the display name
   331  		_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
   332  		if err != nil {
   333  			return fmt.Errorf(
   334  				"Error updating the display name for instance %s: %s", name, err)
   335  		}
   336  
   337  		d.SetPartial("display_name")
   338  	}
   339  
   340  	// Check if the group is changed and if so, update the virtual machine
   341  	if d.HasChange("group") {
   342  		log.Printf("[DEBUG] Group changed for %s, starting update", name)
   343  
   344  		// Create a new parameter struct
   345  		p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
   346  
   347  		// Set the new group
   348  		p.SetGroup(d.Get("group").(string))
   349  
   350  		// Update the display name
   351  		_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
   352  		if err != nil {
   353  			return fmt.Errorf(
   354  				"Error updating the group for instance %s: %s", name, err)
   355  		}
   356  
   357  		d.SetPartial("group")
   358  	}
   359  
   360  	// Attributes that require reboot to update
   361  	if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") || d.HasChange("keypair") {
   362  		// Before we can actually make these changes, the virtual machine must be stopped
   363  		_, err := cs.VirtualMachine.StopVirtualMachine(
   364  			cs.VirtualMachine.NewStopVirtualMachineParams(d.Id()))
   365  		if err != nil {
   366  			return fmt.Errorf(
   367  				"Error stopping instance %s before making changes: %s", name, err)
   368  		}
   369  
   370  		// Check if the name has changed and if so, update the name
   371  		if d.HasChange("name") {
   372  			log.Printf("[DEBUG] Name for %s changed to %s, starting update", d.Id(), name)
   373  
   374  			// Create a new parameter struct
   375  			p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
   376  
   377  			// Set the new name
   378  			p.SetName(name)
   379  
   380  			// Update the display name
   381  			_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
   382  			if err != nil {
   383  				return fmt.Errorf(
   384  					"Error updating the name for instance %s: %s", name, err)
   385  			}
   386  
   387  			d.SetPartial("name")
   388  		}
   389  
   390  		// Check if the service offering is changed and if so, update the offering
   391  		if d.HasChange("service_offering") {
   392  			log.Printf("[DEBUG] Service offering changed for %s, starting update", name)
   393  
   394  			// Retrieve the service_offering ID
   395  			serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
   396  			if e != nil {
   397  				return e.Error()
   398  			}
   399  
   400  			// Create a new parameter struct
   401  			p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid)
   402  
   403  			// Change the service offering
   404  			_, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p)
   405  			if err != nil {
   406  				return fmt.Errorf(
   407  					"Error changing the service offering for instance %s: %s", name, err)
   408  			}
   409  			d.SetPartial("service_offering")
   410  		}
   411  
   412  		if d.HasChange("affinity_group_ids") {
   413  			p := cs.AffinityGroup.NewUpdateVMAffinityGroupParams(d.Id())
   414  			groups := []string{}
   415  
   416  			if ags := d.Get("affinity_group_ids").(*schema.Set); ags.Len() > 0 {
   417  				for _, group := range ags.List() {
   418  					groups = append(groups, group.(string))
   419  				}
   420  			}
   421  
   422  			p.SetAffinitygroupids(groups)
   423  		}
   424  
   425  		if d.HasChange("keypair") {
   426  			log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name)
   427  
   428  			p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string))
   429  
   430  			// Change the ssh keypair
   431  			_, err = cs.SSH.ResetSSHKeyForVirtualMachine(p)
   432  			if err != nil {
   433  				return fmt.Errorf(
   434  					"Error changing the SSH keypair for instance %s: %s", name, err)
   435  			}
   436  			d.SetPartial("keypair")
   437  		}
   438  
   439  		// Start the virtual machine again
   440  		_, err = cs.VirtualMachine.StartVirtualMachine(
   441  			cs.VirtualMachine.NewStartVirtualMachineParams(d.Id()))
   442  		if err != nil {
   443  			return fmt.Errorf(
   444  				"Error starting instance %s after making changes", name)
   445  		}
   446  	}
   447  
   448  	d.Partial(false)
   449  	return resourceCloudStackInstanceRead(d, meta)
   450  }
   451  
   452  func resourceCloudStackInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   453  	cs := meta.(*cloudstack.CloudStackClient)
   454  
   455  	// Create a new parameter struct
   456  	p := cs.VirtualMachine.NewDestroyVirtualMachineParams(d.Id())
   457  
   458  	if d.Get("expunge").(bool) {
   459  		p.SetExpunge(true)
   460  	}
   461  
   462  	log.Printf("[INFO] Destroying instance: %s", d.Get("name").(string))
   463  	if _, err := cs.VirtualMachine.DestroyVirtualMachine(p); err != nil {
   464  		// This is a very poor way to be told the ID does no longer exist :(
   465  		if strings.Contains(err.Error(), fmt.Sprintf(
   466  			"Invalid parameter id value=%s due to incorrect long value format, "+
   467  				"or entity does not exist", d.Id())) {
   468  			return nil
   469  		}
   470  
   471  		return fmt.Errorf("Error destroying instance: %s", err)
   472  	}
   473  
   474  	return nil
   475  }