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