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