Merge pull request #570 from svanharmelen/f-refactor-digitalocean-provider
Refactor to use the schema.Provider approach
This commit is contained in:
commit
fc5a13a1c1
|
@ -3,13 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/terraform/builtin/providers/digitalocean"
|
"github.com/hashicorp/terraform/builtin/providers/digitalocean"
|
||||||
"github.com/hashicorp/terraform/plugin"
|
"github.com/hashicorp/terraform/plugin"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
plugin.Serve(&plugin.ServeOpts{
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
ProviderFunc: func() terraform.ResourceProvider {
|
ProviderFunc: digitalocean.Provider,
|
||||||
return new(digitalocean.ResourceProvider)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,16 @@ package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/pearkes/digitalocean"
|
"github.com/pearkes/digitalocean"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Token string `mapstructure:"token"`
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client() returns a new client for accessing digital
|
// Client() returns a new client for accessing digital ocean.
|
||||||
// ocean.
|
|
||||||
//
|
|
||||||
func (c *Config) Client() (*digitalocean.Client, error) {
|
func (c *Config) Client() (*digitalocean.Client, error) {
|
||||||
|
|
||||||
// If we have env vars set (like in the acc) tests,
|
|
||||||
// we need to override the values passed in here.
|
|
||||||
if v := os.Getenv("DIGITALOCEAN_TOKEN"); v != "" {
|
|
||||||
c.Token = v
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := digitalocean.NewClient(c.Token)
|
client, err := digitalocean.NewClient(c.Token)
|
||||||
|
|
||||||
log.Printf("[INFO] DigitalOcean Client configured for URL: %s", client.URL)
|
log.Printf("[INFO] DigitalOcean Client configured for URL: %s", client.URL)
|
||||||
|
|
|
@ -1,29 +1,48 @@
|
||||||
package digitalocean
|
package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider returns a schema.Provider for DigitalOcean.
|
// Provider returns a schema.Provider for DigitalOcean.
|
||||||
//
|
func Provider() terraform.ResourceProvider {
|
||||||
// NOTE: schema.Provider became available long after the DO provider
|
|
||||||
// was started, so resources may not be converted to this new structure
|
|
||||||
// yet. This is a WIP. To assist with the migration, make sure any resources
|
|
||||||
// you migrate are acceptance tested, then perform the migration.
|
|
||||||
func Provider() *schema.Provider {
|
|
||||||
// TODO: Move the configuration to this
|
|
||||||
|
|
||||||
return &schema.Provider{
|
return &schema.Provider{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"token": &schema.Schema{
|
"token": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
DefaultFunc: envDefaultFunc("DIGITALOCEAN_TOKEN"),
|
||||||
|
Description: "The token key for API operations.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"digitalocean_domain": resourceDomain(),
|
"digitalocean_domain": resourceDigitalOceanDomain(),
|
||||||
"digitalocean_record": resourceRecord(),
|
"digitalocean_droplet": resourceDigitalOceanDroplet(),
|
||||||
|
"digitalocean_record": resourceDigitalOceanRecord(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: providerConfigure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func envDefaultFunc(k string) schema.SchemaDefaultFunc {
|
||||||
|
return func() (interface{}, error) {
|
||||||
|
if v := os.Getenv(k); v != "" {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
config := Config{
|
||||||
|
Token: d.Get("token").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Client()
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,35 @@
|
||||||
package digitalocean
|
package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"digitalocean": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProvider(t *testing.T) {
|
func TestProvider(t *testing.T) {
|
||||||
if err := Provider().InternalValidate(); err != nil {
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
if v := os.Getenv("DIGITALOCEAN_TOKEN"); v == "" {
|
||||||
|
t.Fatal("DIGITALOCEAN_TOKEN must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ import (
|
||||||
"github.com/pearkes/digitalocean"
|
"github.com/pearkes/digitalocean"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceDomain() *schema.Resource {
|
func resourceDigitalOceanDomain() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Create: resourceDomainCreate,
|
Create: resourceDigitalOceanDomainCreate,
|
||||||
Read: resourceDomainRead,
|
Read: resourceDigitalOceanDomainRead,
|
||||||
Delete: resourceDomainDelete,
|
Delete: resourceDigitalOceanDomainDelete,
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"name": &schema.Schema{
|
"name": &schema.Schema{
|
||||||
|
@ -31,18 +31,17 @@ func resourceDomain() *schema.Resource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceDomainCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceDigitalOceanDomainCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
p := meta.(*ResourceProvider)
|
client := meta.(*digitalocean.Client)
|
||||||
client := p.client
|
|
||||||
|
|
||||||
// Build up our creation options
|
// Build up our creation options
|
||||||
opts := digitalocean.CreateDomain{
|
opts := &digitalocean.CreateDomain{
|
||||||
Name: d.Get("name").(string),
|
Name: d.Get("name").(string),
|
||||||
IPAddress: d.Get("ip_address").(string),
|
IPAddress: d.Get("ip_address").(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Domain create configuration: %#v", opts)
|
log.Printf("[DEBUG] Domain create configuration: %#v", opts)
|
||||||
name, err := client.CreateDomain(&opts)
|
name, err := client.CreateDomain(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating Domain: %s", err)
|
return fmt.Errorf("Error creating Domain: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -50,26 +49,11 @@ func resourceDomainCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
d.SetId(name)
|
d.SetId(name)
|
||||||
log.Printf("[INFO] Domain Name: %s", name)
|
log.Printf("[INFO] Domain Name: %s", name)
|
||||||
|
|
||||||
return nil
|
return resourceDigitalOceanDomainRead(d, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceDomainDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceDigitalOceanDomainRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
p := meta.(*ResourceProvider)
|
client := meta.(*digitalocean.Client)
|
||||||
client := p.client
|
|
||||||
|
|
||||||
log.Printf("[INFO] Deleting Domain: %s", d.Id())
|
|
||||||
err := client.DestroyDomain(d.Id())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error deleting Domain: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.SetId("")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resourceDomainRead(d *schema.ResourceData, meta interface{}) error {
|
|
||||||
p := meta.(*ResourceProvider)
|
|
||||||
client := p.client
|
|
||||||
|
|
||||||
domain, err := client.RetrieveDomain(d.Id())
|
domain, err := client.RetrieveDomain(d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -87,3 +71,16 @@ func resourceDomainRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceDigitalOceanDomainDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*digitalocean.Client)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Deleting Domain: %s", d.Id())
|
||||||
|
err := client.DestroyDomain(d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting Domain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ func TestAccDigitalOceanDomain_Basic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckDigitalOceanDomainDestroy(s *terraform.State) error {
|
func testAccCheckDigitalOceanDomainDestroy(s *terraform.State) error {
|
||||||
client := testAccProvider.client
|
client := testAccProvider.Meta().(*digitalocean.Client)
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "digitalocean_domain" {
|
if rs.Type != "digitalocean_domain" {
|
||||||
|
@ -74,7 +74,7 @@ func testAccCheckDigitalOceanDomainExists(n string, domain *digitalocean.Domain)
|
||||||
return fmt.Errorf("No Record ID is set")
|
return fmt.Errorf("No Record ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := testAccProvider.client
|
client := testAccProvider.Meta().(*digitalocean.Client)
|
||||||
|
|
||||||
foundDomain, err := client.RetrieveDomain(rs.Primary.ID)
|
foundDomain, err := client.RetrieveDomain(rs.Primary.ID)
|
||||||
|
|
||||||
|
|
|
@ -6,202 +6,335 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/pearkes/digitalocean"
|
"github.com/pearkes/digitalocean"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resource_digitalocean_droplet_create(
|
func resourceDigitalOceanDroplet() *schema.Resource {
|
||||||
s *terraform.InstanceState,
|
return &schema.Resource{
|
||||||
d *terraform.InstanceDiff,
|
Create: resourceDigitalOceanDropletCreate,
|
||||||
meta interface{}) (*terraform.InstanceState, error) {
|
Read: resourceDigitalOceanDropletRead,
|
||||||
p := meta.(*ResourceProvider)
|
Update: resourceDigitalOceanDropletUpdate,
|
||||||
client := p.client
|
Delete: resourceDigitalOceanDropletDelete,
|
||||||
|
|
||||||
// Merge the diff into the state so that we have all the attributes
|
Schema: map[string]*schema.Schema{
|
||||||
// properly.
|
"image": &schema.Schema{
|
||||||
rs := s.MergeDiff(d)
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"size": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"status": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"locked": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"backups": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ipv6": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ipv6_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ipv6_address_private": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"private_networking": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ipv4_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ipv4_address_private": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ssh_keys": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
"user_data": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDigitalOceanDropletCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*digitalocean.Client)
|
||||||
|
|
||||||
// Build up our creation options
|
// Build up our creation options
|
||||||
opts := digitalocean.CreateDroplet{
|
opts := &digitalocean.CreateDroplet{
|
||||||
Backups: rs.Attributes["backups"],
|
Image: d.Get("image").(string),
|
||||||
Image: rs.Attributes["image"],
|
Name: d.Get("name").(string),
|
||||||
IPV6: rs.Attributes["ipv6"],
|
Region: d.Get("region").(string),
|
||||||
Name: rs.Attributes["name"],
|
Size: d.Get("size").(string),
|
||||||
PrivateNetworking: rs.Attributes["private_networking"],
|
|
||||||
Region: rs.Attributes["region"],
|
|
||||||
Size: rs.Attributes["size"],
|
|
||||||
UserData: rs.Attributes["user_data"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only expand ssh_keys if we have them
|
if attr, ok := d.GetOk("backups"); ok {
|
||||||
if _, ok := rs.Attributes["ssh_keys.#"]; ok {
|
opts.Backups = attr.(string)
|
||||||
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:
|
if attr, ok := d.GetOk("ipv6"); ok && attr.(bool) {
|
||||||
//
|
opts.IPV6 = "true"
|
||||||
// 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 {
|
if attr, ok := d.GetOk("private_networking"); ok && attr.(bool) {
|
||||||
vs = append(vs, v.(string))
|
opts.PrivateNetworking = "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.SSHKeys = vs
|
if attr, ok := d.GetOk("user_data"); ok {
|
||||||
|
opts.UserData = attr.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get configured ssh_keys
|
||||||
|
ssh_keys := d.Get("ssh_keys.#").(int)
|
||||||
|
if ssh_keys > 0 {
|
||||||
|
opts.SSHKeys = make([]string, 0, ssh_keys)
|
||||||
|
for i := 0; i < ssh_keys; i++ {
|
||||||
|
key := fmt.Sprintf("ssh_keys.%d", i)
|
||||||
|
opts.SSHKeys = append(opts.SSHKeys, d.Get(key).(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] Droplet create configuration: %#v", opts)
|
log.Printf("[DEBUG] Droplet create configuration: %#v", opts)
|
||||||
|
|
||||||
id, err := client.CreateDroplet(&opts)
|
id, err := client.CreateDroplet(opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error creating Droplet: %s", err)
|
return fmt.Errorf("Error creating droplet: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the droplets id
|
// Assign the droplets id
|
||||||
rs.ID = id
|
d.SetId(id)
|
||||||
|
|
||||||
log.Printf("[INFO] Droplet ID: %s", id)
|
log.Printf("[INFO] Droplet ID: %s", d.Id())
|
||||||
|
|
||||||
dropletRaw, err := WaitForDropletAttribute(id, "active", []string{"new"}, "status", client)
|
_, err = WaitForDropletAttribute(d, "active", []string{"new"}, "status", meta)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rs, fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Error waiting for droplet (%s) to become ready: %s",
|
"Error waiting for droplet (%s) to become ready: %s", d.Id(), err)
|
||||||
id, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
droplet := dropletRaw.(*digitalocean.Droplet)
|
return resourceDigitalOceanDropletRead(d, meta)
|
||||||
|
|
||||||
// Initialize the connection info
|
|
||||||
rs.Ephemeral.ConnInfo["type"] = "ssh"
|
|
||||||
rs.Ephemeral.ConnInfo["host"] = droplet.IPV4Address("public")
|
|
||||||
|
|
||||||
return resource_digitalocean_droplet_update_state(rs, droplet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_digitalocean_droplet_update(
|
func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
s *terraform.InstanceState,
|
client := meta.(*digitalocean.Client)
|
||||||
d *terraform.InstanceDiff,
|
|
||||||
meta interface{}) (*terraform.InstanceState, error) {
|
|
||||||
p := meta.(*ResourceProvider)
|
|
||||||
client := p.client
|
|
||||||
rs := s.MergeDiff(d)
|
|
||||||
|
|
||||||
var err error
|
// Retrieve the droplet properties for updating the state
|
||||||
|
droplet, err := client.RetrieveDroplet(d.Id())
|
||||||
|
|
||||||
if attr, ok := d.Attributes["size"]; ok {
|
if err != nil {
|
||||||
err = client.PowerOff(rs.ID)
|
return fmt.Errorf("Error retrieving droplet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if droplet.ImageSlug() == "" && droplet.ImageId() != "" {
|
||||||
|
d.Set("image", droplet.ImageId())
|
||||||
|
} else {
|
||||||
|
d.Set("image", droplet.ImageSlug())
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", droplet.Name)
|
||||||
|
d.Set("region", droplet.RegionSlug())
|
||||||
|
d.Set("size", droplet.SizeSlug)
|
||||||
|
d.Set("status", droplet.Status)
|
||||||
|
d.Set("locked", droplet.IsLocked())
|
||||||
|
|
||||||
|
if droplet.IPV6Address("public") != "" {
|
||||||
|
d.Set("ipv6", true)
|
||||||
|
d.Set("ipv6_address", droplet.IPV6Address("public"))
|
||||||
|
d.Set("ipv6_address_private", droplet.IPV6Address("private"))
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("ipv4_address", droplet.IPV4Address("public"))
|
||||||
|
|
||||||
|
if droplet.NetworkingType() == "private" {
|
||||||
|
d.Set("private_networking", true)
|
||||||
|
d.Set("ipv4_address_private", droplet.IPV4Address("private"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the connection info
|
||||||
|
d.SetConnInfo(map[string]string{
|
||||||
|
"type": "ssh",
|
||||||
|
"host": droplet.IPV4Address("public"),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*digitalocean.Client)
|
||||||
|
|
||||||
|
if d.HasChange("size") {
|
||||||
|
oldSize, newSize := d.GetChange("size")
|
||||||
|
|
||||||
|
err := client.PowerOff(d.Id())
|
||||||
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") {
|
if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") {
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error powering off droplet (%s): %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for power off
|
// Wait for power off
|
||||||
_, err = WaitForDropletAttribute(
|
_, err = WaitForDropletAttribute(d, "off", []string{"active"}, "status", client)
|
||||||
rs.ID, "off", []string{"active"}, "status", client)
|
|
||||||
|
|
||||||
err = client.Resize(rs.ID, attr.New)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newErr := power_on_and_wait(rs.ID, client)
|
return fmt.Errorf(
|
||||||
|
"Error waiting for droplet (%s) to become powered off: %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the droplet
|
||||||
|
err = client.Resize(d.Id(), newSize.(string))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
newErr := power_on_and_wait(d, meta)
|
||||||
if newErr != nil {
|
if newErr != nil {
|
||||||
return rs, newErr
|
return fmt.Errorf(
|
||||||
|
"Error powering on droplet (%s) after failed resize: %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
return rs, err
|
return fmt.Errorf(
|
||||||
|
"Error resizing droplet (%s): %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the size to change
|
// Wait for the size to change
|
||||||
_, err = WaitForDropletAttribute(
|
_, err = WaitForDropletAttribute(
|
||||||
rs.ID, attr.New, []string{"", attr.Old}, "size", client)
|
d, newSize.(string), []string{"", oldSize.(string)}, "size", meta)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newErr := power_on_and_wait(rs.ID, client)
|
newErr := power_on_and_wait(d, meta)
|
||||||
if newErr != nil {
|
if newErr != nil {
|
||||||
return rs, newErr
|
return fmt.Errorf(
|
||||||
|
"Error powering on droplet (%s) after waiting for resize to finish: %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error waiting for resize droplet (%s) to finish: %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.PowerOn(rs.ID)
|
err = client.PowerOn(d.Id())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error powering on droplet (%s) after resize: %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for power off
|
// Wait for power off
|
||||||
_, err = WaitForDropletAttribute(
|
_, err = WaitForDropletAttribute(d, "active", []string{"off"}, "status", meta)
|
||||||
rs.ID, "active", []string{"off"}, "status", client)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, ok := d.Attributes["name"]; ok {
|
if d.HasChange("name") {
|
||||||
err = client.Rename(rs.ID, attr.New)
|
oldName, newName := d.GetChange("name")
|
||||||
|
|
||||||
|
// Rename the droplet
|
||||||
|
err := client.Rename(d.Id(), newName.(string))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error renaming droplet (%s): %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the name to change
|
// Wait for the name to change
|
||||||
_, err = WaitForDropletAttribute(
|
_, err = WaitForDropletAttribute(
|
||||||
rs.ID, attr.New, []string{"", attr.Old}, "name", client)
|
d, newName.(string), []string{"", oldName.(string)}, "name", meta)
|
||||||
}
|
|
||||||
|
|
||||||
if attr, ok := d.Attributes["private_networking"]; ok {
|
|
||||||
err = client.Rename(rs.ID, attr.New)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error waiting for rename droplet (%s) to finish: %s", d.Id(), 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 {
|
// As there is no way to disable private networking,
|
||||||
err = client.Rename(rs.ID, attr.New)
|
// we only check if it needs to be enabled
|
||||||
|
if d.HasChange("private_networking") && d.Get("private_networking").(bool) {
|
||||||
|
err := client.EnablePrivateNetworking(d.Id())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error enabling private networking for droplet (%s): %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for ipv6 to turn on/off
|
// Wait for the private_networking to turn on
|
||||||
_, err = WaitForDropletAttribute(
|
_, err = WaitForDropletAttribute(
|
||||||
rs.ID, attr.New, []string{"", attr.Old}, "ipv6", client)
|
d, "true", []string{"", "false"}, "private_networking", meta)
|
||||||
|
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error waiting for private networking to be enabled on for droplet (%s): %s", d.Id(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
droplet, err := resource_digitalocean_droplet_retrieve(rs.ID, client)
|
// As there is no way to disable IPv6, we only check if it needs to be enabled
|
||||||
|
if d.HasChange("ipv6") && d.Get("ipv6").(bool) {
|
||||||
|
err := client.EnableIPV6s(d.Id())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return fmt.Errorf(
|
||||||
|
"Error turning on ipv6 for droplet (%s): %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for ipv6 to turn on
|
||||||
|
_, err = WaitForDropletAttribute(
|
||||||
|
d, "true", []string{"", "false"}, "ipv6", meta)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error waiting for ipv6 to be turned on for droplet (%s): %s", d.Id(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource_digitalocean_droplet_update_state(rs, droplet)
|
return resourceDigitalOceanDropletRead(d, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_digitalocean_droplet_destroy(
|
func resourceDigitalOceanDropletDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
s *terraform.InstanceState,
|
client := meta.(*digitalocean.Client)
|
||||||
meta interface{}) error {
|
|
||||||
p := meta.(*ResourceProvider)
|
|
||||||
client := p.client
|
|
||||||
|
|
||||||
log.Printf("[INFO] Deleting Droplet: %s", s.ID)
|
log.Printf("[INFO] Deleting droplet: %s", d.Id())
|
||||||
|
|
||||||
// Destroy the droplet
|
// Destroy the droplet
|
||||||
err := client.DestroyDroplet(s.ID)
|
err := client.DestroyDroplet(d.Id())
|
||||||
|
|
||||||
// Handle remotely destroyed droplets
|
// Handle remotely destroyed droplets
|
||||||
if err != nil && strings.Contains(err.Error(), "404 Not Found") {
|
if err != nil && strings.Contains(err.Error(), "404 Not Found") {
|
||||||
|
@ -209,140 +342,24 @@ func resource_digitalocean_droplet_destroy(
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error deleting Droplet: %s", err)
|
return fmt.Errorf("Error deleting droplet: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_digitalocean_droplet_refresh(
|
func WaitForDropletAttribute(
|
||||||
s *terraform.InstanceState,
|
d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}) (interface{}, error) {
|
||||||
meta interface{}) (*terraform.InstanceState, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resource_digitalocean_droplet_update_state(s, droplet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resource_digitalocean_droplet_diff(
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
c *terraform.ResourceConfig,
|
|
||||||
meta interface{}) (*terraform.InstanceDiff, error) {
|
|
||||||
|
|
||||||
b := &diff.ResourceBuilder{
|
|
||||||
Attrs: map[string]diff.AttrType{
|
|
||||||
"backups": diff.AttrTypeUpdate,
|
|
||||||
"image": diff.AttrTypeCreate,
|
|
||||||
"ipv6": diff.AttrTypeUpdate,
|
|
||||||
"name": diff.AttrTypeUpdate,
|
|
||||||
"private_networking": diff.AttrTypeUpdate,
|
|
||||||
"region": diff.AttrTypeCreate,
|
|
||||||
"size": diff.AttrTypeUpdate,
|
|
||||||
"ssh_keys": diff.AttrTypeCreate,
|
|
||||||
"user_data": diff.AttrTypeCreate,
|
|
||||||
},
|
|
||||||
|
|
||||||
ComputedAttrs: []string{
|
|
||||||
"backups",
|
|
||||||
"ipv4_address",
|
|
||||||
"ipv4_address_private",
|
|
||||||
"ipv6",
|
|
||||||
"ipv6_address",
|
|
||||||
"ipv6_address_private",
|
|
||||||
"locked",
|
|
||||||
"private_networking",
|
|
||||||
"status",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Diff(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resource_digitalocean_droplet_update_state(
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
droplet *digitalocean.Droplet) (*terraform.InstanceState, 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") != "" {
|
|
||||||
s.Attributes["ipv6"] = "true"
|
|
||||||
s.Attributes["ipv6_address"] = droplet.IPV6Address("public")
|
|
||||||
s.Attributes["ipv6_address_private"] = droplet.IPV6Address("private")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieves an ELB by its ID
|
|
||||||
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",
|
|
||||||
"name",
|
|
||||||
"region",
|
|
||||||
"size",
|
|
||||||
},
|
|
||||||
Optional: []string{
|
|
||||||
"backups",
|
|
||||||
"user_data",
|
|
||||||
"ipv6",
|
|
||||||
"private_networking",
|
|
||||||
"ssh_keys.*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// Wait for the droplet so we can get the networking attributes
|
||||||
// that show up after a while
|
// that show up after a while
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[INFO] Waiting for Droplet (%s) to have %s of %s",
|
"[INFO] Waiting for Droplet (%s) to have %s of %s",
|
||||||
id, attribute, target)
|
d.Id(), attribute, target)
|
||||||
|
|
||||||
stateConf := &resource.StateChangeConf{
|
stateConf := &resource.StateChangeConf{
|
||||||
Pending: pending,
|
Pending: pending,
|
||||||
Target: target,
|
Target: target,
|
||||||
Refresh: new_droplet_state_refresh_func(id, attribute, client),
|
Refresh: new_droplet_state_refresh_func(d, attribute, meta),
|
||||||
Timeout: 10 * time.Minute,
|
Timeout: 10 * time.Minute,
|
||||||
Delay: 10 * time.Second,
|
Delay: 10 * time.Second,
|
||||||
MinTimeout: 3 * time.Second,
|
MinTimeout: 3 * time.Second,
|
||||||
|
@ -351,37 +368,36 @@ func WaitForDropletAttribute(id string, target string, pending []string, attribu
|
||||||
return stateConf.WaitForState()
|
return stateConf.WaitForState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func new_droplet_state_refresh_func(id string, attribute string, client *digitalocean.Client) resource.StateRefreshFunc {
|
// TODO This function still needs a little more refactoring to make it
|
||||||
|
// cleaner and more efficient
|
||||||
|
func new_droplet_state_refresh_func(
|
||||||
|
d *schema.ResourceData, attribute string, meta interface{}) resource.StateRefreshFunc {
|
||||||
|
client := meta.(*digitalocean.Client)
|
||||||
return func() (interface{}, string, error) {
|
return func() (interface{}, string, error) {
|
||||||
// Retrieve the ELB properties for updating the state
|
err := resourceDigitalOceanDropletRead(d, meta)
|
||||||
droplet, err := client.RetrieveDroplet(id)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error on retrieving droplet when waiting: %s", err)
|
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the droplet is locked, continue waiting. We can
|
// If the droplet is locked, continue waiting. We can
|
||||||
// only perform actions on unlocked droplets, so it's
|
// only perform actions on unlocked droplets, so it's
|
||||||
// pointless to look at that status
|
// pointless to look at that status
|
||||||
if droplet.IsLocked() == "true" {
|
if d.Get("locked").(string) == "true" {
|
||||||
log.Println("[DEBUG] Droplet is locked, skipping status check and retrying")
|
log.Println("[DEBUG] Droplet is locked, skipping status check and retrying")
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use our mapping to get back a map of the
|
|
||||||
// droplet properties
|
|
||||||
resourceMap, err := resource_digitalocean_droplet_update_state(
|
|
||||||
&terraform.InstanceState{Attributes: map[string]string{}}, &droplet)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error creating map from droplet: %s", err)
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if we can access our attribute
|
// See if we can access our attribute
|
||||||
if attr, ok := resourceMap.Attributes[attribute]; ok {
|
if attr, ok := d.GetOk(attribute); ok {
|
||||||
return &droplet, attr, nil
|
// Retrieve the droplet properties
|
||||||
|
droplet, err := client.RetrieveDroplet(d.Id())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("Error retrieving droplet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &droplet, attr.(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
|
@ -389,16 +405,16 @@ func new_droplet_state_refresh_func(id string, attribute string, client *digital
|
||||||
}
|
}
|
||||||
|
|
||||||
// Powers on the droplet and waits for it to be active
|
// Powers on the droplet and waits for it to be active
|
||||||
func power_on_and_wait(id string, client *digitalocean.Client) error {
|
func power_on_and_wait(d *schema.ResourceData, meta interface{}) error {
|
||||||
err := client.PowerOn(id)
|
client := meta.(*digitalocean.Client)
|
||||||
|
err := client.PowerOn(d.Id())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for power on
|
// Wait for power on
|
||||||
_, err = WaitForDropletAttribute(
|
_, err = WaitForDropletAttribute(d, "active", []string{"off"}, "status", client)
|
||||||
id, "active", []string{"off"}, "status", client)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -94,7 +94,7 @@ func TestAccDigitalOceanDroplet_PrivateNetworkingIpv6(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckDigitalOceanDropletDestroy(s *terraform.State) error {
|
func testAccCheckDigitalOceanDropletDestroy(s *terraform.State) error {
|
||||||
client := testAccProvider.client
|
client := testAccProvider.Meta().(*digitalocean.Client)
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "digitalocean_droplet" {
|
if rs.Type != "digitalocean_droplet" {
|
||||||
|
@ -207,7 +207,7 @@ func testAccCheckDigitalOceanDropletExists(n string, droplet *digitalocean.Dropl
|
||||||
return fmt.Errorf("No Droplet ID is set")
|
return fmt.Errorf("No Droplet ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := testAccProvider.client
|
client := testAccProvider.Meta().(*digitalocean.Client)
|
||||||
|
|
||||||
retrieveDroplet, err := client.RetrieveDroplet(rs.Primary.ID)
|
retrieveDroplet, err := client.RetrieveDroplet(rs.Primary.ID)
|
||||||
|
|
||||||
|
@ -225,19 +225,23 @@ func testAccCheckDigitalOceanDropletExists(n string, droplet *digitalocean.Dropl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_new_droplet_state_refresh_func(t *testing.T) {
|
// Not sure if this check should remain here as the underlaying
|
||||||
droplet := digitalocean.Droplet{
|
// function is changed and is tested indirectly by almost all
|
||||||
Name: "foobar",
|
// other test already
|
||||||
}
|
//
|
||||||
resourceMap, _ := resource_digitalocean_droplet_update_state(
|
//func Test_new_droplet_state_refresh_func(t *testing.T) {
|
||||||
&terraform.InstanceState{Attributes: map[string]string{}}, &droplet)
|
// droplet := digitalocean.Droplet{
|
||||||
|
// Name: "foobar",
|
||||||
// See if we can access our attribute
|
// }
|
||||||
if _, ok := resourceMap.Attributes["name"]; !ok {
|
// resourceMap, _ := resource_digitalocean_droplet_update_state(
|
||||||
t.Fatalf("bad name: %s", resourceMap.Attributes)
|
// &terraform.InstanceState{Attributes: map[string]string{}}, &droplet)
|
||||||
}
|
//
|
||||||
|
// // See if we can access our attribute
|
||||||
}
|
// if _, ok := resourceMap.Attributes["name"]; !ok {
|
||||||
|
// t.Fatalf("bad name: %s", resourceMap.Attributes)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
const testAccCheckDigitalOceanDropletConfig_basic = `
|
const testAccCheckDigitalOceanDropletConfig_basic = `
|
||||||
resource "digitalocean_droplet" "foobar" {
|
resource "digitalocean_droplet" "foobar" {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import (
|
||||||
"github.com/pearkes/digitalocean"
|
"github.com/pearkes/digitalocean"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceRecord() *schema.Resource {
|
func resourceDigitalOceanRecord() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Create: resourceRecordCreate,
|
Create: resourceDigitalOceanRecordCreate,
|
||||||
Read: resourceRecordRead,
|
Read: resourceDigitalOceanRecordRead,
|
||||||
Update: resourceRecordUpdate,
|
Update: resourceDigitalOceanRecordUpdate,
|
||||||
Delete: resourceRecordDelete,
|
Delete: resourceDigitalOceanRecordDelete,
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"type": &schema.Schema{
|
"type": &schema.Schema{
|
||||||
|
@ -65,9 +65,8 @@ func resourceRecord() *schema.Resource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
p := meta.(*ResourceProvider)
|
client := meta.(*digitalocean.Client)
|
||||||
client := p.client
|
|
||||||
|
|
||||||
newRecord := digitalocean.CreateRecord{
|
newRecord := digitalocean.CreateRecord{
|
||||||
Type: d.Get("type").(string),
|
Type: d.Get("type").(string),
|
||||||
|
@ -87,50 +86,11 @@ func resourceRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
d.SetId(recId)
|
d.SetId(recId)
|
||||||
log.Printf("[INFO] Record ID: %s", d.Id())
|
log.Printf("[INFO] Record ID: %s", d.Id())
|
||||||
|
|
||||||
return resourceRecordRead(d, meta)
|
return resourceDigitalOceanRecordRead(d, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceRecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceDigitalOceanRecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
p := meta.(*ResourceProvider)
|
client := meta.(*digitalocean.Client)
|
||||||
client := p.client
|
|
||||||
|
|
||||||
var updateRecord digitalocean.UpdateRecord
|
|
||||||
if v, ok := d.GetOk("name"); ok {
|
|
||||||
updateRecord.Name = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[DEBUG] record update configuration: %#v", updateRecord)
|
|
||||||
err := client.UpdateRecord(d.Get("domain").(string), d.Id(), &updateRecord)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to update record: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceRecordRead(d, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resourceRecordDelete(d *schema.ResourceData, meta interface{}) error {
|
|
||||||
p := meta.(*ResourceProvider)
|
|
||||||
client := p.client
|
|
||||||
|
|
||||||
log.Printf(
|
|
||||||
"[INFO] Deleting record: %s, %s", d.Get("domain").(string), d.Id())
|
|
||||||
err := client.DestroyRecord(d.Get("domain").(string), d.Id())
|
|
||||||
if err != nil {
|
|
||||||
// If the record is somehow already destroyed, mark as
|
|
||||||
// succesfully gone
|
|
||||||
if strings.Contains(err.Error(), "404 Not Found") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Error deleting record: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resourceRecordRead(d *schema.ResourceData, meta interface{}) error {
|
|
||||||
p := meta.(*ResourceProvider)
|
|
||||||
client := p.client
|
|
||||||
|
|
||||||
rec, err := client.RetrieveRecord(d.Get("domain").(string), d.Id())
|
rec, err := client.RetrieveRecord(d.Get("domain").(string), d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,3 +113,39 @@ func resourceRecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resourceDigitalOceanRecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*digitalocean.Client)
|
||||||
|
|
||||||
|
var updateRecord digitalocean.UpdateRecord
|
||||||
|
if v, ok := d.GetOk("name"); ok {
|
||||||
|
updateRecord.Name = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] record update configuration: %#v", updateRecord)
|
||||||
|
err := client.UpdateRecord(d.Get("domain").(string), d.Id(), &updateRecord)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to update record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceDigitalOceanRecordRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDigitalOceanRecordDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*digitalocean.Client)
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"[INFO] Deleting record: %s, %s", d.Get("domain").(string), d.Id())
|
||||||
|
err := client.DestroyRecord(d.Get("domain").(string), d.Id())
|
||||||
|
if err != nil {
|
||||||
|
// If the record is somehow already destroyed, mark as
|
||||||
|
// succesfully gone
|
||||||
|
if strings.Contains(err.Error(), "404 Not Found") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error deleting record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ func TestAccDigitalOceanRecord_Updated(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckDigitalOceanRecordDestroy(s *terraform.State) error {
|
func testAccCheckDigitalOceanRecordDestroy(s *terraform.State) error {
|
||||||
client := testAccProvider.client
|
client := testAccProvider.Meta().(*digitalocean.Client)
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
if rs.Type != "digitalocean_record" {
|
if rs.Type != "digitalocean_record" {
|
||||||
|
@ -128,7 +128,7 @@ func testAccCheckDigitalOceanRecordExists(n string, record *digitalocean.Record)
|
||||||
return fmt.Errorf("No Record ID is set")
|
return fmt.Errorf("No Record ID is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := testAccProvider.client
|
client := testAccProvider.Meta().(*digitalocean.Client)
|
||||||
|
|
||||||
foundRecord, err := client.RetrieveRecord(rs.Primary.Attributes["domain"], rs.Primary.ID)
|
foundRecord, err := client.RetrieveRecord(rs.Primary.Attributes["domain"], rs.Primary.ID)
|
||||||
|
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/config"
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/pearkes/digitalocean"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResourceProvider struct {
|
|
||||||
Config Config
|
|
||||||
|
|
||||||
client *digitalocean.Client
|
|
||||||
|
|
||||||
// This is the schema.Provider. Eventually this will replace much
|
|
||||||
// of this structure. For now it is an element of it for compatiblity.
|
|
||||||
p *schema.Provider
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Input(
|
|
||||||
input terraform.UIInput,
|
|
||||||
c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
|
|
||||||
return Provider().Input(input, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
|
||||||
prov := Provider()
|
|
||||||
return prov.Validate(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) ValidateResource(
|
|
||||||
t string, c *terraform.ResourceConfig) ([]string, []error) {
|
|
||||||
prov := Provider()
|
|
||||||
if _, ok := prov.ResourcesMap[t]; ok {
|
|
||||||
return prov.ValidateResource(t, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceMap.Validate(t, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error {
|
|
||||||
if _, err := config.Decode(&p.Config, c.Config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[INFO] Initializing DigitalOcean client")
|
|
||||||
var err error
|
|
||||||
p.client, err = p.Config.Client()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the provider, set the meta
|
|
||||||
p.p = Provider()
|
|
||||||
p.p.SetMeta(p)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Apply(
|
|
||||||
info *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
|
|
||||||
if _, ok := p.p.ResourcesMap[info.Type]; ok {
|
|
||||||
return p.p.Apply(info, s, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceMap.Apply(info, s, d, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Diff(
|
|
||||||
info *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState,
|
|
||||||
c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
|
|
||||||
if _, ok := p.p.ResourcesMap[info.Type]; ok {
|
|
||||||
return p.p.Diff(info, s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceMap.Diff(info, s, c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Refresh(
|
|
||||||
info *terraform.InstanceInfo,
|
|
||||||
s *terraform.InstanceState) (*terraform.InstanceState, error) {
|
|
||||||
if _, ok := p.p.ResourcesMap[info.Type]; ok {
|
|
||||||
return p.p.Refresh(info, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceMap.Refresh(info, s, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ResourceProvider) Resources() []terraform.ResourceType {
|
|
||||||
result := resourceMap.Resources()
|
|
||||||
result = append(result, Provider().Resources()...)
|
|
||||||
return result
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testAccProviders map[string]terraform.ResourceProvider
|
|
||||||
var testAccProvider *ResourceProvider
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
testAccProvider = new(ResourceProvider)
|
|
||||||
testAccProviders = map[string]terraform.ResourceProvider{
|
|
||||||
"digitalocean": testAccProvider,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceProvider_impl(t *testing.T) {
|
|
||||||
var _ terraform.ResourceProvider = new(ResourceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceProvider_Configure(t *testing.T) {
|
|
||||||
rp := new(ResourceProvider)
|
|
||||||
var expectedToken string
|
|
||||||
|
|
||||||
if v := os.Getenv("DIGITALOCEAN_TOKEN"); v != "foo" {
|
|
||||||
expectedToken = v
|
|
||||||
} else {
|
|
||||||
expectedToken = "foo"
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := map[string]interface{}{
|
|
||||||
"token": expectedToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawConfig, err := config.NewRawConfig(raw)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rp.Configure(terraform.NewResourceConfig(rawConfig))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := Config{
|
|
||||||
Token: expectedToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(rp.Config, expected) {
|
|
||||||
t.Fatalf("bad: %#v", rp.Config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
|
||||||
if v := os.Getenv("DIGITALOCEAN_TOKEN"); v == "" {
|
|
||||||
t.Fatal("DIGITALOCEAN_TOKEN must be set for acceptance tests")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package digitalocean
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// resourceMap is the mapping of resources we support to their basic
|
|
||||||
// operations. This makes it easy to implement new resource types.
|
|
||||||
var resourceMap *resource.Map
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
resourceMap = &resource.Map{
|
|
||||||
Mapping: map[string]resource.Resource{
|
|
||||||
"digitalocean_droplet": resource.Resource{
|
|
||||||
ConfigValidator: resource_digitalocean_droplet_validation(),
|
|
||||||
Create: resource_digitalocean_droplet_create,
|
|
||||||
Destroy: resource_digitalocean_droplet_destroy,
|
|
||||||
Diff: resource_digitalocean_droplet_diff,
|
|
||||||
Refresh: resource_digitalocean_droplet_refresh,
|
|
||||||
Update: resource_digitalocean_droplet_update,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue