2015-05-31 16:58:14 +02:00
|
|
|
package packet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
"github.com/packethost/packngo"
|
|
|
|
)
|
|
|
|
|
|
|
|
func resourcePacketDevice() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourcePacketDeviceCreate,
|
|
|
|
Read: resourcePacketDeviceRead,
|
|
|
|
Update: resourcePacketDeviceUpdate,
|
|
|
|
Delete: resourcePacketDeviceDelete,
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"project_id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"hostname": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"operating_system": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"facility": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"plan": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"billing_cycle": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"state": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"locked": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"network": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"address": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"gateway": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"family": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"cidr": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"public": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"created": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"updated": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"user_data": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"tags": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourcePacketDeviceCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*packngo.Client)
|
|
|
|
|
|
|
|
createRequest := &packngo.DeviceCreateRequest{
|
|
|
|
HostName: d.Get("hostname").(string),
|
|
|
|
Plan: d.Get("plan").(string),
|
|
|
|
Facility: d.Get("facility").(string),
|
|
|
|
OS: d.Get("operating_system").(string),
|
|
|
|
BillingCycle: d.Get("billing_cycle").(string),
|
|
|
|
ProjectID: d.Get("project_id").(string),
|
|
|
|
}
|
|
|
|
|
|
|
|
if attr, ok := d.GetOk("user_data"); ok {
|
|
|
|
createRequest.UserData = attr.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := d.Get("tags.#").(int)
|
|
|
|
if tags > 0 {
|
|
|
|
createRequest.Tags = make([]string, 0, tags)
|
|
|
|
for i := 0; i < tags; i++ {
|
|
|
|
key := fmt.Sprintf("tags.%d", i)
|
|
|
|
createRequest.Tags = append(createRequest.Tags, d.Get(key).(string))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Device create configuration: %#v", createRequest)
|
|
|
|
|
|
|
|
newDevice, _, err := client.Devices.Create(createRequest)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating device: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign the device id
|
|
|
|
d.SetId(newDevice.ID)
|
|
|
|
|
|
|
|
log.Printf("[INFO] Device ID: %s", d.Id())
|
|
|
|
|
2015-11-10 21:38:08 +01:00
|
|
|
_, err = WaitForDeviceAttribute(d, "active", []string{"queued", "provisioning"}, "state", meta)
|
2015-05-31 16:58:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for device (%s) to become ready: %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resourcePacketDeviceRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourcePacketDeviceRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*packngo.Client)
|
|
|
|
|
|
|
|
// Retrieve the device properties for updating the state
|
|
|
|
device, _, err := client.Devices.Get(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error retrieving device: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("name", device.Hostname)
|
|
|
|
d.Set("plan", device.Plan.Slug)
|
|
|
|
d.Set("facility", device.Facility.Code)
|
|
|
|
d.Set("operating_system", device.OS.Slug)
|
|
|
|
d.Set("state", device.State)
|
|
|
|
d.Set("billing_cycle", device.BillingCycle)
|
|
|
|
d.Set("locked", device.Locked)
|
|
|
|
d.Set("created", device.Created)
|
2015-11-17 06:02:57 +01:00
|
|
|
d.Set("updated", device.Updated)
|
2015-05-31 16:58:14 +02:00
|
|
|
|
|
|
|
tags := make([]string, 0)
|
|
|
|
for _, tag := range device.Tags {
|
|
|
|
tags = append(tags, tag)
|
|
|
|
}
|
|
|
|
d.Set("tags", tags)
|
|
|
|
|
2015-11-17 06:02:57 +01:00
|
|
|
provisionerAddress := ""
|
|
|
|
|
2015-05-31 16:58:14 +02:00
|
|
|
networks := make([]map[string]interface{}, 0, 1)
|
|
|
|
for _, ip := range device.Network {
|
|
|
|
network := make(map[string]interface{})
|
|
|
|
network["address"] = ip.Address
|
|
|
|
network["gateway"] = ip.Gateway
|
|
|
|
network["family"] = ip.Family
|
|
|
|
network["cidr"] = ip.Cidr
|
|
|
|
network["public"] = ip.Public
|
|
|
|
networks = append(networks, network)
|
2015-11-17 06:02:57 +01:00
|
|
|
if ip.Family == 4 && ip.Public == true {
|
|
|
|
provisionerAddress = ip.Address
|
|
|
|
}
|
2015-05-31 16:58:14 +02:00
|
|
|
}
|
|
|
|
d.Set("network", networks)
|
|
|
|
|
2015-11-17 06:02:57 +01:00
|
|
|
log.Printf("[DEBUG] Provisioner Address set to %v", provisionerAddress)
|
|
|
|
|
|
|
|
if provisionerAddress != "" {
|
|
|
|
d.SetConnInfo(map[string]string{
|
|
|
|
"type": "ssh",
|
|
|
|
"host": provisionerAddress,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-05-31 16:58:14 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourcePacketDeviceUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*packngo.Client)
|
|
|
|
|
|
|
|
if d.HasChange("locked") && d.Get("locked").(bool) {
|
|
|
|
_, err := client.Devices.Lock(d.Id())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error locking device (%s): %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
} else if d.HasChange("locked") {
|
|
|
|
_, err := client.Devices.Unlock(d.Id())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error unlocking device (%s): %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resourcePacketDeviceRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourcePacketDeviceDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*packngo.Client)
|
|
|
|
|
|
|
|
log.Printf("[INFO] Deleting device: %s", d.Id())
|
|
|
|
if _, err := client.Devices.Delete(d.Id()); err != nil {
|
|
|
|
return fmt.Errorf("Error deleting device: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func WaitForDeviceAttribute(
|
|
|
|
d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}) (interface{}, error) {
|
|
|
|
// Wait for the device so we can get the networking attributes
|
|
|
|
// that show up after a while
|
|
|
|
log.Printf(
|
|
|
|
"[INFO] Waiting for device (%s) to have %s of %s",
|
|
|
|
d.Id(), attribute, target)
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: pending,
|
|
|
|
Target: target,
|
|
|
|
Refresh: newDeviceStateRefreshFunc(d, attribute, meta),
|
|
|
|
Timeout: 60 * time.Minute,
|
|
|
|
Delay: 10 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
return stateConf.WaitForState()
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDeviceStateRefreshFunc(
|
|
|
|
d *schema.ResourceData, attribute string, meta interface{}) resource.StateRefreshFunc {
|
|
|
|
client := meta.(*packngo.Client)
|
|
|
|
return func() (interface{}, string, error) {
|
|
|
|
err := resourcePacketDeviceRead(d, meta)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if we can access our attribute
|
|
|
|
if attr, ok := d.GetOk(attribute); ok {
|
|
|
|
// Retrieve the device properties
|
|
|
|
device, _, err := client.Devices.Get(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", fmt.Errorf("Error retrieving device: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &device, attr.(string), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, "", nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Powers on the device and waits for it to be active
|
|
|
|
func powerOnAndWait(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*packngo.Client)
|
|
|
|
_, err := client.Devices.PowerOn(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for power on
|
|
|
|
_, err = WaitForDeviceAttribute(d, "active", []string{"off"}, "state", client)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|