github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/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/flatmap"
    10  	"github.com/hashicorp/terraform/helper/config"
    11  	"github.com/hashicorp/terraform/helper/diff"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/terraform"
    14  	"github.com/pearkes/digitalocean"
    15  )
    16  
    17  func resource_digitalocean_droplet_create(
    18  	s *terraform.ResourceState,
    19  	d *terraform.ResourceDiff,
    20  	meta interface{}) (*terraform.ResourceState, error) {
    21  	p := meta.(*ResourceProvider)
    22  	client := p.client
    23  
    24  	// Merge the diff into the state so that we have all the attributes
    25  	// properly.
    26  	rs := s.MergeDiff(d)
    27  
    28  	// Build up our creation options
    29  	opts := digitalocean.CreateDroplet{
    30  		Backups:           rs.Attributes["backups"],
    31  		Image:             rs.Attributes["image"],
    32  		IPV6:              rs.Attributes["ipv6"],
    33  		Name:              rs.Attributes["name"],
    34  		PrivateNetworking: rs.Attributes["private_networking"],
    35  		Region:            rs.Attributes["region"],
    36  		Size:              rs.Attributes["size"],
    37  	}
    38  
    39  	// Only expand ssh_keys if we have them
    40  	if _, ok := rs.Attributes["ssh_keys.#"]; ok {
    41  		v := flatmap.Expand(rs.Attributes, "ssh_keys").([]interface{})
    42  		if len(v) > 0 {
    43  			vs := make([]string, 0, len(v))
    44  
    45  			// here we special case the * expanded lists. For example:
    46  			//
    47  			//	 ssh_keys = ["${digitalocean_key.foo.*.id}"]
    48  			//
    49  			if len(v) == 1 && strings.Contains(v[0].(string), ",") {
    50  				vs = strings.Split(v[0].(string), ",")
    51  			}
    52  
    53  			for _, v := range v {
    54  				vs = append(vs, v.(string))
    55  			}
    56  
    57  			opts.SSHKeys = vs
    58  		}
    59  	}
    60  
    61  	log.Printf("[DEBUG] Droplet create configuration: %#v", opts)
    62  
    63  	id, err := client.CreateDroplet(&opts)
    64  
    65  	if err != nil {
    66  		return nil, fmt.Errorf("Error creating Droplet: %s", err)
    67  	}
    68  
    69  	// Assign the droplets id
    70  	rs.ID = id
    71  
    72  	log.Printf("[INFO] Droplet ID: %s", id)
    73  
    74  	dropletRaw, err := WaitForDropletAttribute(id, "active", []string{"new"}, "status", client)
    75  
    76  	if err != nil {
    77  		return rs, fmt.Errorf(
    78  			"Error waiting for droplet (%s) to become ready: %s",
    79  			id, err)
    80  	}
    81  
    82  	droplet := dropletRaw.(*digitalocean.Droplet)
    83  
    84  	// Initialize the connection info
    85  	rs.ConnInfo["type"] = "ssh"
    86  	rs.ConnInfo["host"] = droplet.IPV4Address()
    87  
    88  	return resource_digitalocean_droplet_update_state(rs, droplet)
    89  }
    90  
    91  func resource_digitalocean_droplet_update(
    92  	s *terraform.ResourceState,
    93  	d *terraform.ResourceDiff,
    94  	meta interface{}) (*terraform.ResourceState, error) {
    95  	p := meta.(*ResourceProvider)
    96  	client := p.client
    97  	rs := s.MergeDiff(d)
    98  
    99  	var err error
   100  
   101  	if attr, ok := d.Attributes["size"]; ok {
   102  		err = client.PowerOff(rs.ID)
   103  
   104  		if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") {
   105  			return s, err
   106  		}
   107  
   108  		// Wait for power off
   109  		_, err = WaitForDropletAttribute(
   110  			rs.ID, "off", []string{"active"}, "status", client)
   111  
   112  		err = client.Resize(rs.ID, attr.New)
   113  
   114  		if err != nil {
   115  			return rs, err
   116  		}
   117  
   118  		// Wait for the size to change
   119  		_, err = WaitForDropletAttribute(
   120  			rs.ID, attr.New, []string{"", attr.Old}, "size", client)
   121  
   122  		if err != nil {
   123  			return s, err
   124  		}
   125  
   126  		err = client.PowerOn(rs.ID)
   127  
   128  		if err != nil {
   129  			return s, err
   130  		}
   131  
   132  		// Wait for power off
   133  		_, err = WaitForDropletAttribute(
   134  			rs.ID, "active", []string{"off"}, "status", client)
   135  
   136  		if err != nil {
   137  			return s, err
   138  		}
   139  	}
   140  
   141  	if attr, ok := d.Attributes["name"]; ok {
   142  		err = client.Rename(rs.ID, attr.New)
   143  
   144  		if err != nil {
   145  			return s, err
   146  		}
   147  
   148  		// Wait for the name to change
   149  		_, err = WaitForDropletAttribute(
   150  			rs.ID, attr.New, []string{"", attr.Old}, "name", client)
   151  	}
   152  
   153  	if attr, ok := d.Attributes["private_networking"]; ok {
   154  		err = client.Rename(rs.ID, attr.New)
   155  
   156  		if err != nil {
   157  			return s, err
   158  		}
   159  
   160  		// Wait for the private_networking to turn on/off
   161  		_, err = WaitForDropletAttribute(
   162  			rs.ID, attr.New, []string{"", attr.Old}, "private_networking", client)
   163  	}
   164  
   165  	if attr, ok := d.Attributes["ipv6"]; ok {
   166  		err = client.Rename(rs.ID, attr.New)
   167  
   168  		if err != nil {
   169  			return s, err
   170  		}
   171  
   172  		// Wait for ipv6 to turn on/off
   173  		_, err = WaitForDropletAttribute(
   174  			rs.ID, attr.New, []string{"", attr.Old}, "ipv6", client)
   175  	}
   176  
   177  	droplet, err := resource_digitalocean_droplet_retrieve(rs.ID, client)
   178  
   179  	if err != nil {
   180  		return s, err
   181  	}
   182  
   183  	return resource_digitalocean_droplet_update_state(rs, droplet)
   184  }
   185  
   186  func resource_digitalocean_droplet_destroy(
   187  	s *terraform.ResourceState,
   188  	meta interface{}) error {
   189  	p := meta.(*ResourceProvider)
   190  	client := p.client
   191  
   192  	log.Printf("[INFO] Deleting Droplet: %s", s.ID)
   193  
   194  	// Destroy the droplet
   195  	err := client.DestroyDroplet(s.ID)
   196  
   197  	// Handle remotely destroyed droplets
   198  	if err != nil && strings.Contains(err.Error(), "404 Not Found") {
   199  		return nil
   200  	}
   201  
   202  	if err != nil {
   203  		return fmt.Errorf("Error deleting Droplet: %s", err)
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func resource_digitalocean_droplet_refresh(
   210  	s *terraform.ResourceState,
   211  	meta interface{}) (*terraform.ResourceState, error) {
   212  	p := meta.(*ResourceProvider)
   213  	client := p.client
   214  
   215  	droplet, err := resource_digitalocean_droplet_retrieve(s.ID, client)
   216  
   217  	// Handle remotely destroyed droplets
   218  	if err != nil && strings.Contains(err.Error(), "404 Not Found") {
   219  		return nil, nil
   220  	}
   221  
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	return resource_digitalocean_droplet_update_state(s, droplet)
   227  }
   228  
   229  func resource_digitalocean_droplet_diff(
   230  	s *terraform.ResourceState,
   231  	c *terraform.ResourceConfig,
   232  	meta interface{}) (*terraform.ResourceDiff, error) {
   233  
   234  	b := &diff.ResourceBuilder{
   235  		Attrs: map[string]diff.AttrType{
   236  			"backups":            diff.AttrTypeUpdate,
   237  			"image":              diff.AttrTypeCreate,
   238  			"ipv6":               diff.AttrTypeUpdate,
   239  			"name":               diff.AttrTypeUpdate,
   240  			"private_networking": diff.AttrTypeUpdate,
   241  			"region":             diff.AttrTypeCreate,
   242  			"size":               diff.AttrTypeUpdate,
   243  			"ssh_keys":           diff.AttrTypeCreate,
   244  		},
   245  
   246  		ComputedAttrs: []string{
   247  			"backups",
   248  			"ipv4_address",
   249  			"ipv6",
   250  			"ipv6_address",
   251  			"locked",
   252  			"private_networking",
   253  			"status",
   254  		},
   255  	}
   256  
   257  	return b.Diff(s, c)
   258  }
   259  
   260  func resource_digitalocean_droplet_update_state(
   261  	s *terraform.ResourceState,
   262  	droplet *digitalocean.Droplet) (*terraform.ResourceState, error) {
   263  
   264  	s.Attributes["name"] = droplet.Name
   265  	s.Attributes["region"] = droplet.RegionSlug()
   266  
   267  	if droplet.ImageSlug() == "" && droplet.ImageId() != "" {
   268  		s.Attributes["image"] = droplet.ImageId()
   269  	} else {
   270  		s.Attributes["image"] = droplet.ImageSlug()
   271  	}
   272  
   273  	if droplet.IPV6Address() != "" {
   274  		s.Attributes["ipv6"] = "true"
   275  		s.Attributes["ipv6_address"] = droplet.IPV6Address()
   276  	}
   277  
   278  	s.Attributes["ipv4_address"] = droplet.IPV4Address()
   279  	s.Attributes["locked"] = droplet.IsLocked()
   280  	s.Attributes["private_networking"] = droplet.NetworkingType()
   281  	s.Attributes["size"] = droplet.SizeSlug()
   282  	s.Attributes["status"] = droplet.Status
   283  
   284  	return s, nil
   285  }
   286  
   287  // retrieves an ELB by it's ID
   288  func resource_digitalocean_droplet_retrieve(id string, client *digitalocean.Client) (*digitalocean.Droplet, error) {
   289  	// Retrieve the ELB properties for updating the state
   290  	droplet, err := client.RetrieveDroplet(id)
   291  
   292  	if err != nil {
   293  		return nil, fmt.Errorf("Error retrieving droplet: %s", err)
   294  	}
   295  
   296  	return &droplet, nil
   297  }
   298  
   299  func resource_digitalocean_droplet_validation() *config.Validator {
   300  	return &config.Validator{
   301  		Required: []string{
   302  			"image",
   303  			"name",
   304  			"region",
   305  			"size",
   306  		},
   307  		Optional: []string{
   308  			"backups",
   309  			"ipv6",
   310  			"private_networking",
   311  			"ssh_keys.*",
   312  		},
   313  	}
   314  }
   315  
   316  func WaitForDropletAttribute(id string, target string, pending []string, attribute string, client *digitalocean.Client) (interface{}, error) {
   317  	// Wait for the droplet so we can get the networking attributes
   318  	// that show up after a while
   319  	log.Printf(
   320  		"[INFO] Waiting for Droplet (%s) to have %s of %s",
   321  		id, attribute, target)
   322  
   323  	stateConf := &resource.StateChangeConf{
   324  		Pending:    pending,
   325  		Target:     target,
   326  		Refresh:    new_droplet_state_refresh_func(id, attribute, client),
   327  		Timeout:    10 * time.Minute,
   328  		Delay:      10 * time.Second,
   329  		MinTimeout: 3 * time.Second,
   330  	}
   331  
   332  	return stateConf.WaitForState()
   333  }
   334  
   335  func new_droplet_state_refresh_func(id string, attribute string, client *digitalocean.Client) resource.StateRefreshFunc {
   336  	return func() (interface{}, string, error) {
   337  		// Retrieve the ELB properties for updating the state
   338  		droplet, err := client.RetrieveDroplet(id)
   339  
   340  		if err != nil {
   341  			log.Printf("Error on retrieving droplet when waiting: %s", err)
   342  			return nil, "", err
   343  		}
   344  
   345  		// If the droplet is locked, continue waiting. We can
   346  		// only perform actions on unlocked droplets, so it's
   347  		// pointless to look at that status
   348  		if droplet.IsLocked() == "true" {
   349  			log.Println("[DEBUG] Droplet is locked, skipping status check and retrying")
   350  			return nil, "", nil
   351  		}
   352  
   353  		// Use our mapping to get back a map of the
   354  		// droplet properties
   355  		resourceMap, err := resource_digitalocean_droplet_update_state(
   356  			&terraform.ResourceState{Attributes: map[string]string{}}, &droplet)
   357  
   358  		if err != nil {
   359  			log.Printf("Error creating map from droplet: %s", err)
   360  			return nil, "", err
   361  		}
   362  
   363  		// See if we can access our attribute
   364  		if attr, ok := resourceMap.Attributes[attribute]; ok {
   365  			return &droplet, attr, nil
   366  		}
   367  
   368  		return nil, "", nil
   369  	}
   370  }