github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/builtin/providers/digitalocean/resource_digitalocean_droplet.go (about)

     1  package digitalocean
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"github.com/pearkes/digitalocean"
    12  )
    13  
    14  func resourceDigitalOceanDroplet() *schema.Resource {
    15  	return &schema.Resource{
    16  		Create: resourceDigitalOceanDropletCreate,
    17  		Read:   resourceDigitalOceanDropletRead,
    18  		Update: resourceDigitalOceanDropletUpdate,
    19  		Delete: resourceDigitalOceanDropletDelete,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"image": &schema.Schema{
    23  				Type:     schema.TypeString,
    24  				Required: true,
    25  				ForceNew: true,
    26  			},
    27  
    28  			"name": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Required: true,
    31  			},
    32  
    33  			"region": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Required: true,
    36  				ForceNew: true,
    37  			},
    38  
    39  			"size": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Required: true,
    42  			},
    43  
    44  			"status": &schema.Schema{
    45  				Type:     schema.TypeString,
    46  				Computed: true,
    47  			},
    48  
    49  			"locked": &schema.Schema{
    50  				Type:     schema.TypeString,
    51  				Computed: true,
    52  			},
    53  
    54  			"backups": &schema.Schema{
    55  				Type:     schema.TypeString,
    56  				Optional: true,
    57  			},
    58  
    59  			"ipv6": &schema.Schema{
    60  				Type:     schema.TypeBool,
    61  				Optional: true,
    62  			},
    63  
    64  			"ipv6_address": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Computed: true,
    67  			},
    68  
    69  			"ipv6_address_private": &schema.Schema{
    70  				Type:     schema.TypeString,
    71  				Computed: true,
    72  			},
    73  
    74  			"private_networking": &schema.Schema{
    75  				Type:     schema.TypeBool,
    76  				Optional: true,
    77  			},
    78  
    79  			"ipv4_address": &schema.Schema{
    80  				Type:     schema.TypeString,
    81  				Computed: true,
    82  			},
    83  
    84  			"ipv4_address_private": &schema.Schema{
    85  				Type:     schema.TypeString,
    86  				Computed: true,
    87  			},
    88  
    89  			"ssh_keys": &schema.Schema{
    90  				Type:     schema.TypeList,
    91  				Optional: true,
    92  				Elem:     &schema.Schema{Type: schema.TypeString},
    93  			},
    94  
    95  			"user_data": &schema.Schema{
    96  				Type:     schema.TypeString,
    97  				Optional: true,
    98  			},
    99  		},
   100  	}
   101  }
   102  
   103  func resourceDigitalOceanDropletCreate(d *schema.ResourceData, meta interface{}) error {
   104  	client := meta.(*digitalocean.Client)
   105  
   106  	// Build up our creation options
   107  	opts := &digitalocean.CreateDroplet{
   108  		Image:  d.Get("image").(string),
   109  		Name:   d.Get("name").(string),
   110  		Region: d.Get("region").(string),
   111  		Size:   d.Get("size").(string),
   112  	}
   113  
   114  	if attr, ok := d.GetOk("backups"); ok {
   115  		opts.Backups = attr.(string)
   116  	}
   117  
   118  	if attr, ok := d.GetOk("ipv6"); ok && attr.(bool) {
   119  		opts.IPV6 = "true"
   120  	}
   121  
   122  	if attr, ok := d.GetOk("private_networking"); ok && attr.(bool) {
   123  		opts.PrivateNetworking = "true"
   124  	}
   125  
   126  	if attr, ok := d.GetOk("user_data"); ok {
   127  		opts.UserData = attr.(string)
   128  	}
   129  
   130  	// Get configured ssh_keys
   131  	ssh_keys := d.Get("ssh_keys.#").(int)
   132  	if ssh_keys > 0 {
   133  		opts.SSHKeys = make([]string, 0, ssh_keys)
   134  		for i := 0; i < ssh_keys; i++ {
   135  			key := fmt.Sprintf("ssh_keys.%d", i)
   136  			opts.SSHKeys = append(opts.SSHKeys, d.Get(key).(string))
   137  		}
   138  	}
   139  
   140  	log.Printf("[DEBUG] Droplet create configuration: %#v", opts)
   141  
   142  	id, err := client.CreateDroplet(opts)
   143  
   144  	if err != nil {
   145  		return fmt.Errorf("Error creating droplet: %s", err)
   146  	}
   147  
   148  	// Assign the droplets id
   149  	d.SetId(id)
   150  
   151  	log.Printf("[INFO] Droplet ID: %s", d.Id())
   152  
   153  	_, err = WaitForDropletAttribute(d, "active", []string{"new"}, "status", meta)
   154  
   155  	if err != nil {
   156  		return fmt.Errorf(
   157  			"Error waiting for droplet (%s) to become ready: %s", d.Id(), err)
   158  	}
   159  
   160  	return resourceDigitalOceanDropletRead(d, meta)
   161  }
   162  
   163  func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) error {
   164  	client := meta.(*digitalocean.Client)
   165  
   166  	// Retrieve the droplet properties for updating the state
   167  	droplet, err := client.RetrieveDroplet(d.Id())
   168  
   169  	if err != nil {
   170  		return fmt.Errorf("Error retrieving droplet: %s", err)
   171  	}
   172  
   173  	if droplet.ImageSlug() != "" {
   174  		d.Set("image", droplet.ImageSlug())
   175  	} else {
   176  		d.Set("image", droplet.ImageId())
   177  	}
   178  
   179  	d.Set("name", droplet.Name)
   180  	d.Set("region", droplet.RegionSlug())
   181  	d.Set("size", droplet.SizeSlug)
   182  	d.Set("status", droplet.Status)
   183  	d.Set("locked", droplet.IsLocked())
   184  
   185  	if droplet.IPV6Address("public") != "" {
   186  		d.Set("ipv6", true)
   187  		d.Set("ipv6_address", droplet.IPV6Address("public"))
   188  		d.Set("ipv6_address_private", droplet.IPV6Address("private"))
   189  	}
   190  
   191  	d.Set("ipv4_address", droplet.IPV4Address("public"))
   192  
   193  	if droplet.NetworkingType() == "private" {
   194  		d.Set("private_networking", true)
   195  		d.Set("ipv4_address_private", droplet.IPV4Address("private"))
   196  	}
   197  
   198  	// Initialize the connection info
   199  	d.SetConnInfo(map[string]string{
   200  		"type": "ssh",
   201  		"host": droplet.IPV4Address("public"),
   202  	})
   203  
   204  	return nil
   205  }
   206  
   207  func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{}) error {
   208  	client := meta.(*digitalocean.Client)
   209  
   210  	if d.HasChange("size") {
   211  		oldSize, newSize := d.GetChange("size")
   212  
   213  		err := client.PowerOff(d.Id())
   214  
   215  		if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") {
   216  			return fmt.Errorf(
   217  				"Error powering off droplet (%s): %s", d.Id(), err)
   218  		}
   219  
   220  		// Wait for power off
   221  		_, err = WaitForDropletAttribute(d, "off", []string{"active"}, "status", client)
   222  
   223  		if err != nil {
   224  			return fmt.Errorf(
   225  				"Error waiting for droplet (%s) to become powered off: %s", d.Id(), err)
   226  		}
   227  
   228  		// Resize the droplet
   229  		err = client.Resize(d.Id(), newSize.(string))
   230  
   231  		if err != nil {
   232  			newErr := powerOnAndWait(d, meta)
   233  			if newErr != nil {
   234  				return fmt.Errorf(
   235  					"Error powering on droplet (%s) after failed resize: %s", d.Id(), err)
   236  			}
   237  			return fmt.Errorf(
   238  				"Error resizing droplet (%s): %s", d.Id(), err)
   239  		}
   240  
   241  		// Wait for the size to change
   242  		_, err = WaitForDropletAttribute(
   243  			d, newSize.(string), []string{"", oldSize.(string)}, "size", meta)
   244  
   245  		if err != nil {
   246  			newErr := powerOnAndWait(d, meta)
   247  			if newErr != nil {
   248  				return fmt.Errorf(
   249  					"Error powering on droplet (%s) after waiting for resize to finish: %s", d.Id(), err)
   250  			}
   251  			return fmt.Errorf(
   252  				"Error waiting for resize droplet (%s) to finish: %s", d.Id(), err)
   253  		}
   254  
   255  		err = client.PowerOn(d.Id())
   256  
   257  		if err != nil {
   258  			return fmt.Errorf(
   259  				"Error powering on droplet (%s) after resize: %s", d.Id(), err)
   260  		}
   261  
   262  		// Wait for power off
   263  		_, err = WaitForDropletAttribute(d, "active", []string{"off"}, "status", meta)
   264  
   265  		if err != nil {
   266  			return err
   267  		}
   268  	}
   269  
   270  	if d.HasChange("name") {
   271  		oldName, newName := d.GetChange("name")
   272  
   273  		// Rename the droplet
   274  		err := client.Rename(d.Id(), newName.(string))
   275  
   276  		if err != nil {
   277  			return fmt.Errorf(
   278  				"Error renaming droplet (%s): %s", d.Id(), err)
   279  		}
   280  
   281  		// Wait for the name to change
   282  		_, err = WaitForDropletAttribute(
   283  			d, newName.(string), []string{"", oldName.(string)}, "name", meta)
   284  
   285  		if err != nil {
   286  			return fmt.Errorf(
   287  				"Error waiting for rename droplet (%s) to finish: %s", d.Id(), err)
   288  		}
   289  	}
   290  
   291  	// As there is no way to disable private networking,
   292  	// we only check if it needs to be enabled
   293  	if d.HasChange("private_networking") && d.Get("private_networking").(bool) {
   294  		err := client.EnablePrivateNetworking(d.Id())
   295  
   296  		if err != nil {
   297  			return fmt.Errorf(
   298  				"Error enabling private networking for droplet (%s): %s", d.Id(), err)
   299  		}
   300  
   301  		// Wait for the private_networking to turn on
   302  		_, err = WaitForDropletAttribute(
   303  			d, "true", []string{"", "false"}, "private_networking", meta)
   304  
   305  		return fmt.Errorf(
   306  			"Error waiting for private networking to be enabled on for droplet (%s): %s", d.Id(), err)
   307  	}
   308  
   309  	// As there is no way to disable IPv6, we only check if it needs to be enabled
   310  	if d.HasChange("ipv6") && d.Get("ipv6").(bool) {
   311  		err := client.EnableIPV6s(d.Id())
   312  
   313  		if err != nil {
   314  			return fmt.Errorf(
   315  				"Error turning on ipv6 for droplet (%s): %s", d.Id(), err)
   316  		}
   317  
   318  		// Wait for ipv6 to turn on
   319  		_, err = WaitForDropletAttribute(
   320  			d, "true", []string{"", "false"}, "ipv6", meta)
   321  
   322  		if err != nil {
   323  			return fmt.Errorf(
   324  				"Error waiting for ipv6 to be turned on for droplet (%s): %s", d.Id(), err)
   325  		}
   326  	}
   327  
   328  	return resourceDigitalOceanDropletRead(d, meta)
   329  }
   330  
   331  func resourceDigitalOceanDropletDelete(d *schema.ResourceData, meta interface{}) error {
   332  	client := meta.(*digitalocean.Client)
   333  
   334  	log.Printf("[INFO] Deleting droplet: %s", d.Id())
   335  
   336  	// Destroy the droplet
   337  	err := client.DestroyDroplet(d.Id())
   338  
   339  	// Handle remotely destroyed droplets
   340  	if err != nil && strings.Contains(err.Error(), "404 Not Found") {
   341  		return nil
   342  	}
   343  
   344  	if err != nil {
   345  		return fmt.Errorf("Error deleting droplet: %s", err)
   346  	}
   347  
   348  	return nil
   349  }
   350  
   351  func WaitForDropletAttribute(
   352  	d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}) (interface{}, error) {
   353  	// Wait for the droplet so we can get the networking attributes
   354  	// that show up after a while
   355  	log.Printf(
   356  		"[INFO] Waiting for droplet (%s) to have %s of %s",
   357  		d.Id(), attribute, target)
   358  
   359  	stateConf := &resource.StateChangeConf{
   360  		Pending:    pending,
   361  		Target:     target,
   362  		Refresh:    newDropletStateRefreshFunc(d, attribute, meta),
   363  		Timeout:    10 * time.Minute,
   364  		Delay:      10 * time.Second,
   365  		MinTimeout: 3 * time.Second,
   366  	}
   367  
   368  	return stateConf.WaitForState()
   369  }
   370  
   371  // TODO This function still needs a little more refactoring to make it
   372  // cleaner and more efficient
   373  func newDropletStateRefreshFunc(
   374  	d *schema.ResourceData, attribute string, meta interface{}) resource.StateRefreshFunc {
   375  	client := meta.(*digitalocean.Client)
   376  	return func() (interface{}, string, error) {
   377  		err := resourceDigitalOceanDropletRead(d, meta)
   378  
   379  		if err != nil {
   380  			return nil, "", err
   381  		}
   382  
   383  		// If the droplet is locked, continue waiting. We can
   384  		// only perform actions on unlocked droplets, so it's
   385  		// pointless to look at that status
   386  		if d.Get("locked").(string) == "true" {
   387  			log.Println("[DEBUG] Droplet is locked, skipping status check and retrying")
   388  			return nil, "", nil
   389  		}
   390  
   391  		// See if we can access our attribute
   392  		if attr, ok := d.GetOk(attribute); ok {
   393  			// Retrieve the droplet properties
   394  			droplet, err := client.RetrieveDroplet(d.Id())
   395  
   396  			if err != nil {
   397  				return nil, "", fmt.Errorf("Error retrieving droplet: %s", err)
   398  			}
   399  
   400  			return &droplet, attr.(string), nil
   401  		}
   402  
   403  		return nil, "", nil
   404  	}
   405  }
   406  
   407  // Powers on the droplet and waits for it to be active
   408  func powerOnAndWait(d *schema.ResourceData, meta interface{}) error {
   409  	client := meta.(*digitalocean.Client)
   410  	err := client.PowerOn(d.Id())
   411  
   412  	if err != nil {
   413  		return err
   414  	}
   415  
   416  	// Wait for power on
   417  	_, err = WaitForDropletAttribute(d, "active", []string{"off"}, "status", client)
   418  
   419  	if err != nil {
   420  		return err
   421  	}
   422  
   423  	return nil
   424  }