terraform/builtin/providers/digitalocean/resource_digitalocean_dropl...

409 lines
9.9 KiB
Go
Raw Normal View History

2014-07-19 02:20:28 +02:00
package digitalocean
import (
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/terraform/flatmap"
"github.com/hashicorp/terraform/helper/config"
"github.com/hashicorp/terraform/helper/diff"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/pearkes/digitalocean"
)
func resource_digitalocean_droplet_create(
s *terraform.ResourceState,
d *terraform.ResourceDiff,
meta interface{}) (*terraform.ResourceState, error) {
p := meta.(*ResourceProvider)
client := p.client
// Merge the diff into the state so that we have all the attributes
// properly.
rs := s.MergeDiff(d)
// Build up our creation options
opts := digitalocean.CreateDroplet{
Backups: rs.Attributes["backups"],
Image: rs.Attributes["image"],
2014-07-19 02:20:28 +02:00
IPV6: rs.Attributes["ipv6"],
Name: rs.Attributes["name"],
2014-07-19 02:20:28 +02:00
PrivateNetworking: rs.Attributes["private_networking"],
Region: rs.Attributes["region"],
Size: rs.Attributes["size"],
UserData: rs.Attributes["user_data"],
2014-07-19 02:20:28 +02:00
}
// Only expand ssh_keys if we have them
if _, ok := rs.Attributes["ssh_keys.#"]; ok {
v := flatmap.Expand(rs.Attributes, "ssh_keys").([]interface{})
if len(v) > 0 {
vs := make([]string, 0, len(v))
// here we special case the * expanded lists. For example:
//
// ssh_keys = ["${digitalocean_key.foo.*.id}"]
//
if len(v) == 1 && strings.Contains(v[0].(string), ",") {
vs = strings.Split(v[0].(string), ",")
}
for _, v := range v {
vs = append(vs, v.(string))
}
opts.SSHKeys = vs
}
}
log.Printf("[DEBUG] Droplet create configuration: %#v", opts)
id, err := client.CreateDroplet(&opts)
if err != nil {
return nil, fmt.Errorf("Error creating Droplet: %s", err)
}
// Assign the droplets id
rs.ID = id
log.Printf("[INFO] Droplet ID: %s", id)
dropletRaw, err := WaitForDropletAttribute(id, "active", []string{"new"}, "status", client)
2014-07-19 02:20:28 +02:00
if err != nil {
return rs, fmt.Errorf(
"Error waiting for droplet (%s) to become ready: %s",
id, err)
}
droplet := dropletRaw.(*digitalocean.Droplet)
// Initialize the connection info
rs.ConnInfo["type"] = "ssh"
rs.ConnInfo["host"] = droplet.IPV4Address("public")
2014-07-19 02:20:28 +02:00
return resource_digitalocean_droplet_update_state(rs, droplet)
}
func resource_digitalocean_droplet_update(
s *terraform.ResourceState,
d *terraform.ResourceDiff,
meta interface{}) (*terraform.ResourceState, error) {
2014-07-19 18:33:25 +02:00
p := meta.(*ResourceProvider)
client := p.client
rs := s.MergeDiff(d)
var err error
if attr, ok := d.Attributes["size"]; ok {
err = client.PowerOff(rs.ID)
if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") {
return s, err
}
// Wait for power off
_, err = WaitForDropletAttribute(
rs.ID, "off", []string{"active"}, "status", client)
err = client.Resize(rs.ID, attr.New)
if err != nil {
newErr := power_on_and_wait(rs.ID, client)
if newErr != nil {
return rs, newErr
}
2014-07-19 18:33:25 +02:00
return rs, err
}
// Wait for the size to change
_, err = WaitForDropletAttribute(
rs.ID, attr.New, []string{"", attr.Old}, "size", client)
if err != nil {
newErr := power_on_and_wait(rs.ID, client)
if newErr != nil {
return rs, newErr
}
2014-07-19 18:33:25 +02:00
return s, err
}
err = client.PowerOn(rs.ID)
if err != nil {
return s, err
}
// Wait for power off
_, err = WaitForDropletAttribute(
rs.ID, "active", []string{"off"}, "status", client)
if err != nil {
return s, err
}
}
if attr, ok := d.Attributes["name"]; ok {
err = client.Rename(rs.ID, attr.New)
if err != nil {
return s, err
}
// Wait for the name to change
_, err = WaitForDropletAttribute(
rs.ID, attr.New, []string{"", attr.Old}, "name", client)
}
if attr, ok := d.Attributes["private_networking"]; ok {
err = client.Rename(rs.ID, attr.New)
if err != nil {
return s, err
}
// Wait for the private_networking to turn on/off
_, err = WaitForDropletAttribute(
rs.ID, attr.New, []string{"", attr.Old}, "private_networking", client)
}
if attr, ok := d.Attributes["ipv6"]; ok {
err = client.Rename(rs.ID, attr.New)
2014-07-19 18:33:25 +02:00
if err != nil {
return s, err
}
// Wait for ipv6 to turn on/off
_, err = WaitForDropletAttribute(
rs.ID, attr.New, []string{"", attr.Old}, "ipv6", client)
}
2014-07-19 18:33:25 +02:00
droplet, err := resource_digitalocean_droplet_retrieve(rs.ID, client)
2014-07-19 02:20:28 +02:00
2014-07-19 18:33:25 +02:00
if err != nil {
return s, err
}
2014-07-19 02:20:28 +02:00
2014-07-19 18:33:25 +02:00
return resource_digitalocean_droplet_update_state(rs, droplet)
2014-07-19 02:20:28 +02:00
}
func resource_digitalocean_droplet_destroy(
s *terraform.ResourceState,
meta interface{}) error {
p := meta.(*ResourceProvider)
client := p.client
log.Printf("[INFO] Deleting Droplet: %s", s.ID)
// Destroy the droplet
err := client.DestroyDroplet(s.ID)
// Handle remotely destroyed droplets
2014-07-24 23:57:23 +02:00
if err != nil && strings.Contains(err.Error(), "404 Not Found") {
return nil
}
2014-07-19 02:20:28 +02:00
if err != nil {
return fmt.Errorf("Error deleting Droplet: %s", err)
}
return nil
}
func resource_digitalocean_droplet_refresh(
s *terraform.ResourceState,
meta interface{}) (*terraform.ResourceState, error) {
p := meta.(*ResourceProvider)
client := p.client
droplet, err := resource_digitalocean_droplet_retrieve(s.ID, client)
// Handle remotely destroyed droplets
if err != nil && strings.Contains(err.Error(), "404 Not Found") {
return nil, nil
}
2014-07-19 02:20:28 +02:00
if err != nil {
return nil, err
}
return resource_digitalocean_droplet_update_state(s, droplet)
}
func resource_digitalocean_droplet_diff(
s *terraform.ResourceState,
c *terraform.ResourceConfig,
meta interface{}) (*terraform.ResourceDiff, error) {
b := &diff.ResourceBuilder{
Attrs: map[string]diff.AttrType{
"backups": diff.AttrTypeUpdate,
"image": diff.AttrTypeCreate,
2014-07-19 02:20:28 +02:00
"ipv6": diff.AttrTypeUpdate,
"name": diff.AttrTypeUpdate,
2014-07-19 02:20:28 +02:00
"private_networking": diff.AttrTypeUpdate,
"region": diff.AttrTypeCreate,
2014-07-19 18:33:25 +02:00
"size": diff.AttrTypeUpdate,
2014-07-19 02:20:28 +02:00
"ssh_keys": diff.AttrTypeCreate,
"user_data": diff.AttrTypeCreate,
2014-07-19 02:20:28 +02:00
},
ComputedAttrs: []string{
"backups",
2014-07-19 02:20:28 +02:00
"ipv4_address",
"ipv4_address_private",
"ipv6",
2014-07-19 02:20:28 +02:00
"ipv6_address",
"ipv6_address_private",
"locked",
2014-07-19 02:20:28 +02:00
"private_networking",
"status",
2014-07-19 02:20:28 +02:00
},
}
return b.Diff(s, c)
}
func resource_digitalocean_droplet_update_state(
s *terraform.ResourceState,
droplet *digitalocean.Droplet) (*terraform.ResourceState, error) {
s.Attributes["name"] = droplet.Name
s.Attributes["region"] = droplet.RegionSlug()
if droplet.ImageSlug() == "" && droplet.ImageId() != "" {
s.Attributes["image"] = droplet.ImageId()
} else {
s.Attributes["image"] = droplet.ImageSlug()
}
if droplet.IPV6Address("public") != "" {
2014-07-19 18:33:25 +02:00
s.Attributes["ipv6"] = "true"
s.Attributes["ipv6_address"] = droplet.IPV6Address("public")
s.Attributes["ipv6_address_private"] = droplet.IPV6Address("private")
2014-07-19 18:33:25 +02:00
}
s.Attributes["ipv4_address"] = droplet.IPV4Address("public")
s.Attributes["locked"] = droplet.IsLocked()
if droplet.NetworkingType() == "private" {
s.Attributes["private_networking"] = "true"
s.Attributes["ipv4_address_private"] = droplet.IPV4Address("private")
}
s.Attributes["size"] = droplet.SizeSlug()
s.Attributes["status"] = droplet.Status
2014-07-19 02:20:28 +02:00
return s, nil
}
// retrieves an ELB by its ID
2014-07-19 02:20:28 +02:00
func resource_digitalocean_droplet_retrieve(id string, client *digitalocean.Client) (*digitalocean.Droplet, error) {
// Retrieve the ELB properties for updating the state
droplet, err := client.RetrieveDroplet(id)
if err != nil {
return nil, fmt.Errorf("Error retrieving droplet: %s", err)
}
return &droplet, nil
}
func resource_digitalocean_droplet_validation() *config.Validator {
return &config.Validator{
Required: []string{
"image",
2014-07-19 02:20:28 +02:00
"name",
"region",
"size",
},
Optional: []string{
"backups",
"user_data",
2014-07-19 02:20:28 +02:00
"ipv6",
"private_networking",
"ssh_keys.*",
2014-07-19 02:20:28 +02:00
},
}
}
func WaitForDropletAttribute(id string, target string, pending []string, attribute string, client *digitalocean.Client) (interface{}, error) {
// Wait for the droplet so we can get the networking attributes
// that show up after a while
log.Printf(
2014-07-19 18:33:25 +02:00
"[INFO] Waiting for Droplet (%s) to have %s of %s",
id, attribute, target)
stateConf := &resource.StateChangeConf{
Pending: pending,
Target: target,
Refresh: new_droplet_state_refresh_func(id, attribute, client),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
return stateConf.WaitForState()
}
func new_droplet_state_refresh_func(id string, attribute string, client *digitalocean.Client) resource.StateRefreshFunc {
2014-07-19 02:20:28 +02:00
return func() (interface{}, string, error) {
// Retrieve the ELB properties for updating the state
droplet, err := client.RetrieveDroplet(id)
2014-07-19 02:20:28 +02:00
if err != nil {
log.Printf("Error on retrieving droplet when waiting: %s", err)
return nil, "", err
2014-07-19 02:20:28 +02:00
}
// If the droplet is locked, continue waiting. We can
// only perform actions on unlocked droplets, so it's
// pointless to look at that status
if droplet.IsLocked() == "true" {
log.Println("[DEBUG] Droplet is locked, skipping status check and retrying")
return nil, "", nil
}
// Use our mapping to get back a map of the
// droplet properties
resourceMap, err := resource_digitalocean_droplet_update_state(
&terraform.ResourceState{Attributes: map[string]string{}}, &droplet)
2014-07-19 02:20:28 +02:00
if err != nil {
log.Printf("Error creating map from droplet: %s", err)
2014-07-19 02:20:28 +02:00
return nil, "", err
}
// See if we can access our attribute
if attr, ok := resourceMap.Attributes[attribute]; ok {
return &droplet, attr, nil
}
return nil, "", nil
2014-07-19 02:20:28 +02:00
}
}
// Powers on the droplet and waits for it to be active
func power_on_and_wait(id string, client *digitalocean.Client) error {
err := client.PowerOn(id)
if err != nil {
return err
}
2014-08-01 17:05:32 +02:00
// Wait for power on
_, err = WaitForDropletAttribute(
id, "active", []string{"off"}, "status", client)
if err != nil {
return err
}
return nil
}