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