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