2017-04-04 00:24:53 +02:00
|
|
|
package opc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2017-04-04 20:36:51 +02:00
|
|
|
"strconv"
|
2017-04-04 00:24:53 +02:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2017-04-05 22:40:05 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/validation"
|
2017-04-04 00:24:53 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func resourceInstance() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceInstanceCreate,
|
|
|
|
Read: resourceInstanceRead,
|
|
|
|
Delete: resourceInstanceDelete,
|
|
|
|
Importer: &schema.ResourceImporter{
|
|
|
|
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
|
|
|
combined := strings.Split(d.Id(), "/")
|
|
|
|
if len(combined) != 2 {
|
|
|
|
return nil, fmt.Errorf("Invalid ID specified. Must be in the form of instance_name/instance_id. Got: %s", d.Id())
|
|
|
|
}
|
|
|
|
d.Set("name", combined[0])
|
|
|
|
d.SetId(combined[1])
|
|
|
|
return []*schema.ResourceData{d}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
/////////////////////////
|
|
|
|
// Required Attributes //
|
|
|
|
/////////////////////////
|
|
|
|
"name": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"shape": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
/////////////////////////
|
|
|
|
// Optional Attributes //
|
|
|
|
/////////////////////////
|
|
|
|
"instance_attributes": {
|
2017-04-06 00:14:11 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
ValidateFunc: validation.ValidateJsonString,
|
2017-04-04 00:24:53 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
"boot_order": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeInt},
|
|
|
|
},
|
|
|
|
|
|
|
|
"hostname": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"image_list": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"label": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"networking_info": {
|
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"dns": {
|
|
|
|
// Required for Shared Network Interface, will default if unspecified, however
|
|
|
|
// Optional for IP Network Interface
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"index": {
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
ForceNew: true,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ip_address": {
|
|
|
|
// Optional, IP Network only
|
|
|
|
Type: schema.TypeString,
|
|
|
|
ForceNew: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ip_network": {
|
|
|
|
// Required for an IP Network Interface
|
|
|
|
Type: schema.TypeString,
|
|
|
|
ForceNew: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"mac_address": {
|
|
|
|
// Optional, IP Network Only
|
|
|
|
Type: schema.TypeString,
|
|
|
|
ForceNew: true,
|
|
|
|
Computed: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"name_servers": {
|
|
|
|
// Optional, IP Network + Shared Network
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"nat": {
|
|
|
|
// Optional for IP Network
|
|
|
|
// Required for Shared Network
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"search_domains": {
|
|
|
|
// Optional, IP Network + Shared Network
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"sec_lists": {
|
|
|
|
// Required, Shared Network only. Will default if unspecified however
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"shared_network": {
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
"vnic": {
|
|
|
|
// Optional, IP Network only.
|
|
|
|
Type: schema.TypeString,
|
|
|
|
ForceNew: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"vnic_sets": {
|
|
|
|
// Optional, IP Network only.
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: func(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
buf.WriteString(fmt.Sprintf("%d-", m["index"].(int)))
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["vnic"].(string)))
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["nat"]))
|
|
|
|
return hashcode.String(buf.String())
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"reverse_dns": {
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ssh_keys": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"storage": {
|
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"index": {
|
2017-04-05 22:40:05 +02:00
|
|
|
Type: schema.TypeInt,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
ValidateFunc: validation.IntBetween(1, 10),
|
2017-04-04 00:24:53 +02:00
|
|
|
},
|
|
|
|
"volume": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
"name": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"tags": tagsForceNewSchema(),
|
|
|
|
|
|
|
|
/////////////////////////
|
|
|
|
// Computed Attributes //
|
|
|
|
/////////////////////////
|
|
|
|
"attributes": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"availability_domain": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"domain": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"entry": {
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"fingerprint": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"image_format": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"ip_address": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"placement_requirements": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"platform": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"priority": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"quota_reservation": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"relationships": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"resolvers": {
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
|
|
|
|
"site": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"start_time": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"state": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
2017-04-05 22:40:05 +02:00
|
|
|
"vcable": {
|
2017-04-04 00:24:53 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"virtio": {
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"vnc_address": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*compute.Client).Instances()
|
|
|
|
|
|
|
|
// Get Required Attributes
|
|
|
|
input := &compute.CreateInstanceInput{
|
|
|
|
Name: d.Get("name").(string),
|
|
|
|
Shape: d.Get("shape").(string),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get optional instance attributes
|
2017-04-06 00:14:11 +02:00
|
|
|
attributes, attrErr := getInstanceAttributes(d)
|
|
|
|
if attrErr != nil {
|
|
|
|
return attrErr
|
|
|
|
}
|
|
|
|
|
|
|
|
if attributes != nil {
|
2017-04-04 00:24:53 +02:00
|
|
|
input.Attributes = attributes
|
|
|
|
}
|
|
|
|
|
|
|
|
if bootOrder := getIntList(d, "boot_order"); len(bootOrder) > 0 {
|
|
|
|
input.BootOrder = bootOrder
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("hostname"); ok {
|
|
|
|
input.Hostname = v.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("image_list"); ok {
|
|
|
|
input.ImageList = v.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("label"); ok {
|
|
|
|
input.Label = v.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
interfaces, err := readNetworkInterfacesFromConfig(d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if interfaces != nil {
|
|
|
|
input.Networking = interfaces
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("reverse_dns"); ok {
|
|
|
|
input.ReverseDNS = v.(bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
if sshKeys := getStringList(d, "ssh_keys"); len(sshKeys) > 0 {
|
|
|
|
input.SSHKeys = sshKeys
|
|
|
|
}
|
|
|
|
|
2017-04-05 22:40:05 +02:00
|
|
|
storage := getStorageAttachments(d)
|
|
|
|
if len(storage) > 0 {
|
|
|
|
input.Storage = storage
|
|
|
|
}
|
2017-04-04 00:24:53 +02:00
|
|
|
|
|
|
|
if tags := getStringList(d, "tags"); len(tags) > 0 {
|
|
|
|
input.Tags = tags
|
|
|
|
}
|
|
|
|
|
|
|
|
result, err := client.CreateInstance(input)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating instance %s: %s", input.Name, err)
|
|
|
|
}
|
|
|
|
|
2017-04-07 18:06:45 +02:00
|
|
|
log.Printf("[DEBUG] Created instance %s: %#v", input.Name, result.ID)
|
2017-04-04 00:24:53 +02:00
|
|
|
|
|
|
|
d.SetId(result.ID)
|
|
|
|
|
|
|
|
return resourceInstanceRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*compute.Client).Instances()
|
|
|
|
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
|
|
|
|
input := &compute.GetInstanceInput{
|
|
|
|
ID: d.Id(),
|
|
|
|
Name: name,
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Reading state of instance %s", name)
|
|
|
|
result, err := client.GetInstance(input)
|
|
|
|
if err != nil {
|
|
|
|
// Instance doesn't exist
|
|
|
|
if compute.WasNotFoundError(err) {
|
|
|
|
log.Printf("[DEBUG] Instance %s not found", name)
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Error reading instance %s: %s", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Instance '%s' found", name)
|
|
|
|
|
|
|
|
// Update attributes
|
|
|
|
return updateInstanceAttributes(d, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateInstanceAttributes(d *schema.ResourceData, instance *compute.InstanceInfo) error {
|
|
|
|
d.Set("name", instance.Name)
|
|
|
|
d.Set("shape", instance.Shape)
|
|
|
|
|
|
|
|
if err := setInstanceAttributes(d, instance.Attributes); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if attrs, ok := d.GetOk("instance_attributes"); ok && attrs != nil {
|
|
|
|
d.Set("instance_attributes", attrs.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := setIntList(d, "boot_order", instance.BootOrder); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Set("hostname", instance.Hostname)
|
|
|
|
d.Set("image_list", instance.ImageList)
|
|
|
|
d.Set("label", instance.Label)
|
|
|
|
|
|
|
|
if err := readNetworkInterfaces(d, instance.Networking); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("reverse_dns", instance.ReverseDNS)
|
|
|
|
if err := setStringList(d, "ssh_keys", instance.SSHKeys); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-05 22:40:05 +02:00
|
|
|
if err := readStorageAttachments(d, instance.Storage); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-04-04 00:24:53 +02:00
|
|
|
|
|
|
|
if err := setStringList(d, "tags", instance.Tags); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Set("availability_domain", instance.AvailabilityDomain)
|
|
|
|
d.Set("domain", instance.Domain)
|
|
|
|
d.Set("entry", instance.Entry)
|
|
|
|
d.Set("fingerprint", instance.Fingerprint)
|
|
|
|
d.Set("image_format", instance.ImageFormat)
|
|
|
|
d.Set("ip_address", instance.IPAddress)
|
|
|
|
|
|
|
|
if err := setStringList(d, "placement_requirements", instance.PlacementRequirements); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("platform", instance.Platform)
|
|
|
|
d.Set("priority", instance.Priority)
|
|
|
|
d.Set("quota_reservation", instance.QuotaReservation)
|
|
|
|
|
|
|
|
if err := setStringList(d, "relationships", instance.Relationships); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := setStringList(d, "resolvers", instance.Resolvers); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Set("site", instance.Site)
|
|
|
|
d.Set("start_time", instance.StartTime)
|
|
|
|
d.Set("state", instance.State)
|
|
|
|
|
|
|
|
if err := setStringList(d, "tags", instance.Tags); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-05 22:40:05 +02:00
|
|
|
d.Set("vcable", instance.VCableID)
|
2017-04-04 00:24:53 +02:00
|
|
|
d.Set("virtio", instance.Virtio)
|
|
|
|
d.Set("vnc_address", instance.VNC)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*compute.Client).Instances()
|
|
|
|
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
|
|
|
|
input := &compute.DeleteInstanceInput{
|
|
|
|
ID: d.Id(),
|
|
|
|
Name: name,
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] Deleting instance %s", name)
|
|
|
|
|
|
|
|
if err := client.DeleteInstance(input); err != nil {
|
|
|
|
return fmt.Errorf("Error deleting instance %s: %s", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-04-05 22:40:05 +02:00
|
|
|
func getStorageAttachments(d *schema.ResourceData) []compute.StorageAttachmentInput {
|
|
|
|
storageAttachments := []compute.StorageAttachmentInput{}
|
2017-04-04 00:24:53 +02:00
|
|
|
storage := d.Get("storage").(*schema.Set)
|
|
|
|
for _, i := range storage.List() {
|
|
|
|
attrs := i.(map[string]interface{})
|
|
|
|
storageAttachments = append(storageAttachments, compute.StorageAttachmentInput{
|
|
|
|
Index: attrs["index"].(int),
|
|
|
|
Volume: attrs["volume"].(string),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return storageAttachments
|
2017-04-05 22:40:05 +02:00
|
|
|
}
|
2017-04-04 00:24:53 +02:00
|
|
|
|
|
|
|
// Parses instance_attributes from a string to a map[string]interface and returns any errors.
|
|
|
|
func getInstanceAttributes(d *schema.ResourceData) (map[string]interface{}, error) {
|
|
|
|
var attrs map[string]interface{}
|
|
|
|
|
|
|
|
// Empty instance attributes
|
|
|
|
attributes, ok := d.GetOk("instance_attributes")
|
|
|
|
if !ok {
|
|
|
|
return attrs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal([]byte(attributes.(string)), &attrs); err != nil {
|
|
|
|
return attrs, fmt.Errorf("Cannot parse attributes as json: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reads attributes from the returned instance object, and sets the computed attributes string
|
|
|
|
// as JSON
|
|
|
|
func setInstanceAttributes(d *schema.ResourceData, attributes map[string]interface{}) error {
|
|
|
|
// Shouldn't ever get nil attributes on an instance, but protect against the case either way
|
|
|
|
if attributes == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := json.Marshal(attributes)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error marshalling returned attributes: %s", err)
|
|
|
|
}
|
|
|
|
return d.Set("attributes", string(b))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Populates and validates shared network and ip network interfaces to return the of map
|
|
|
|
// objects needed to create/update an instance's networking_info
|
|
|
|
func readNetworkInterfacesFromConfig(d *schema.ResourceData) (map[string]compute.NetworkingInfo, error) {
|
|
|
|
interfaces := make(map[string]compute.NetworkingInfo)
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("networking_info"); ok {
|
|
|
|
vL := v.(*schema.Set).List()
|
|
|
|
for _, v := range vL {
|
|
|
|
ni := v.(map[string]interface{})
|
|
|
|
index, ok := ni["index"].(int)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Index not specified for network interface: %v", ni)
|
|
|
|
}
|
|
|
|
|
|
|
|
deviceIndex := fmt.Sprintf("eth%d", index)
|
|
|
|
|
|
|
|
// Verify that the network interface doesn't already exist
|
|
|
|
if _, ok := interfaces[deviceIndex]; ok {
|
|
|
|
return nil, fmt.Errorf("Duplicate Network interface at eth%d already specified", index)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if we're creating a shared network interface or an IP Network interface
|
|
|
|
info := compute.NetworkingInfo{}
|
|
|
|
var err error
|
|
|
|
if ni["shared_network"].(bool) {
|
|
|
|
// Populate shared network parameters
|
|
|
|
info, err = readSharedNetworkFromConfig(ni)
|
2017-04-06 19:05:52 +02:00
|
|
|
// Set 'model' since we're configuring a shared network interface
|
|
|
|
info.Model = compute.NICDefaultModel
|
2017-04-04 00:24:53 +02:00
|
|
|
} else {
|
|
|
|
// Populate IP Network Parameters
|
|
|
|
info, err = readIPNetworkFromConfig(ni)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// And you may find yourself in a beautiful house, with a beautiful wife
|
|
|
|
// And you may ask yourself, well, how did I get here?
|
|
|
|
interfaces[deviceIndex] = info
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return interfaces, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reads a networking_info config block as a shared network interface
|
|
|
|
func readSharedNetworkFromConfig(ni map[string]interface{}) (compute.NetworkingInfo, error) {
|
|
|
|
info := compute.NetworkingInfo{}
|
|
|
|
// Validate the shared network
|
|
|
|
if err := validateSharedNetwork(ni); err != nil {
|
|
|
|
return info, err
|
|
|
|
}
|
|
|
|
// Populate shared network fields; checking type casting
|
|
|
|
dns := []string{}
|
|
|
|
if v, ok := ni["dns"]; ok && v != nil {
|
|
|
|
for _, d := range v.([]interface{}) {
|
|
|
|
dns = append(dns, d.(string))
|
|
|
|
}
|
|
|
|
if len(dns) > 0 {
|
|
|
|
info.DNS = dns
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := ni["model"].(string); ok && v != "" {
|
|
|
|
info.Model = compute.NICModel(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
nats := []string{}
|
|
|
|
if v, ok := ni["nat"]; ok && v != nil {
|
|
|
|
for _, nat := range v.([]interface{}) {
|
|
|
|
nats = append(nats, nat.(string))
|
|
|
|
}
|
|
|
|
if len(nats) > 0 {
|
|
|
|
info.Nat = nats
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
slists := []string{}
|
|
|
|
if v, ok := ni["sec_lists"]; ok && v != nil {
|
|
|
|
for _, slist := range v.([]interface{}) {
|
|
|
|
slists = append(slists, slist.(string))
|
|
|
|
}
|
|
|
|
if len(slists) > 0 {
|
|
|
|
info.SecLists = slists
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nservers := []string{}
|
|
|
|
if v, ok := ni["name_servers"]; ok && v != nil {
|
|
|
|
for _, nserver := range v.([]interface{}) {
|
|
|
|
nservers = append(nservers, nserver.(string))
|
|
|
|
}
|
|
|
|
if len(nservers) > 0 {
|
|
|
|
info.NameServers = nservers
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sdomains := []string{}
|
|
|
|
if v, ok := ni["search_domains"]; ok && v != nil {
|
|
|
|
for _, sdomain := range v.([]interface{}) {
|
|
|
|
sdomains = append(sdomains, sdomain.(string))
|
|
|
|
}
|
|
|
|
if len(sdomains) > 0 {
|
|
|
|
info.SearchDomains = sdomains
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unfortunately this cannot take place during plan-phase, because we currently cannot have a validation
|
|
|
|
// function based off of multiple fields in the supplied schema.
|
|
|
|
func validateSharedNetwork(ni map[string]interface{}) error {
|
|
|
|
// A Shared Networking Interface MUST have the following attributes set:
|
|
|
|
// - "nat"
|
|
|
|
// The following attributes _cannot_ be set for a shared network:
|
|
|
|
// - "ip_address"
|
|
|
|
// - "ip_network"
|
|
|
|
// - "mac_address"
|
|
|
|
// - "vnic"
|
|
|
|
// - "vnic_sets"
|
|
|
|
|
|
|
|
if _, ok := ni["nat"]; !ok {
|
|
|
|
return fmt.Errorf("'nat' field needs to be set for a Shared Networking Interface")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strings only
|
|
|
|
nilAttrs := []string{
|
|
|
|
"ip_address",
|
|
|
|
"ip_network",
|
|
|
|
"mac_address",
|
|
|
|
"vnic",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range nilAttrs {
|
|
|
|
if d, ok := ni[v]; ok && d.(string) != "" {
|
|
|
|
return fmt.Errorf("%q field cannot be set in a Shared Networking Interface", v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if _, ok := ni["vnic_sets"].([]string); ok {
|
|
|
|
return fmt.Errorf("%q field cannot be set in a Shared Networking Interface", "vnic_sets")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Populates fields for an IP Network
|
|
|
|
func readIPNetworkFromConfig(ni map[string]interface{}) (compute.NetworkingInfo, error) {
|
|
|
|
info := compute.NetworkingInfo{}
|
|
|
|
// Validate the IP Network
|
|
|
|
if err := validateIPNetwork(ni); err != nil {
|
|
|
|
return info, err
|
|
|
|
}
|
|
|
|
// Populate fields
|
|
|
|
if v, ok := ni["ip_network"].(string); ok && v != "" {
|
|
|
|
info.IPNetwork = v
|
|
|
|
}
|
|
|
|
|
|
|
|
dns := []string{}
|
|
|
|
if v, ok := ni["dns"]; ok && v != nil {
|
|
|
|
for _, d := range v.([]interface{}) {
|
|
|
|
dns = append(dns, d.(string))
|
|
|
|
}
|
|
|
|
if len(dns) > 0 {
|
|
|
|
info.DNS = dns
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := ni["ip_address"].(string); ok && v != "" {
|
|
|
|
info.IPAddress = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := ni["mac_address"].(string); ok && v != "" {
|
|
|
|
info.MACAddress = v
|
|
|
|
}
|
|
|
|
|
|
|
|
nservers := []string{}
|
|
|
|
if v, ok := ni["name_servers"]; ok && v != nil {
|
|
|
|
for _, nserver := range v.([]interface{}) {
|
|
|
|
nservers = append(nservers, nserver.(string))
|
|
|
|
}
|
|
|
|
if len(nservers) > 0 {
|
|
|
|
info.NameServers = nservers
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nats := []string{}
|
|
|
|
if v, ok := ni["nat"]; ok && v != nil {
|
|
|
|
for _, nat := range v.([]interface{}) {
|
|
|
|
nats = append(nats, nat.(string))
|
|
|
|
}
|
|
|
|
if len(nats) > 0 {
|
|
|
|
info.Nat = nats
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sdomains := []string{}
|
|
|
|
if v, ok := ni["search_domains"]; ok && v != nil {
|
|
|
|
for _, sdomain := range v.([]interface{}) {
|
|
|
|
sdomains = append(sdomains, sdomain.(string))
|
|
|
|
}
|
|
|
|
if len(sdomains) > 0 {
|
|
|
|
info.SearchDomains = sdomains
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := ni["vnic"].(string); ok && v != "" {
|
|
|
|
info.Vnic = v
|
|
|
|
}
|
|
|
|
|
|
|
|
vnicSets := []string{}
|
|
|
|
if v, ok := ni["vnic_sets"]; ok && v != nil {
|
|
|
|
for _, vnic := range v.([]interface{}) {
|
|
|
|
vnicSets = append(vnicSets, vnic.(string))
|
|
|
|
}
|
|
|
|
if len(vnicSets) > 0 {
|
|
|
|
info.VnicSets = vnicSets
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validates an IP Network config block
|
|
|
|
func validateIPNetwork(ni map[string]interface{}) error {
|
|
|
|
// An IP Networking Interface MUST have the following attributes set:
|
|
|
|
// - "ip_network"
|
|
|
|
|
|
|
|
// Required to be set
|
|
|
|
if d, ok := ni["ip_network"]; !ok || d.(string) == "" {
|
|
|
|
return fmt.Errorf("'ip_network' field is required for an IP Network interface")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reads network interfaces from the config
|
|
|
|
func readNetworkInterfaces(d *schema.ResourceData, ifaces map[string]compute.NetworkingInfo) error {
|
|
|
|
result := make([]map[string]interface{}, 0)
|
|
|
|
|
|
|
|
// Nil check for import case
|
|
|
|
if ifaces == nil {
|
|
|
|
return d.Set("networking_info", result)
|
|
|
|
}
|
|
|
|
|
2017-04-04 20:36:51 +02:00
|
|
|
for index, iface := range ifaces {
|
2017-04-04 00:24:53 +02:00
|
|
|
res := make(map[string]interface{})
|
2017-04-04 20:36:51 +02:00
|
|
|
// The index returned from the SDK holds the full device_index from the instance.
|
|
|
|
// For users convenience, we simply allow them to specify the integer equivalent of the device_index
|
|
|
|
// so a user could implement several network interfaces via `count`.
|
|
|
|
// Convert the full device_index `ethN` to `N` as an integer.
|
|
|
|
index := strings.TrimPrefix(index, "eth")
|
|
|
|
indexInt, err := strconv.Atoi(index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
res["index"] = indexInt
|
|
|
|
|
|
|
|
// Set the proper attributes for this specific network interface
|
2017-04-04 00:24:53 +02:00
|
|
|
if iface.DNS != nil {
|
|
|
|
res["dns"] = iface.DNS
|
|
|
|
}
|
|
|
|
if iface.IPAddress != "" {
|
|
|
|
res["ip_address"] = iface.IPAddress
|
|
|
|
}
|
|
|
|
if iface.IPNetwork != "" {
|
|
|
|
res["ip_network"] = iface.IPNetwork
|
|
|
|
}
|
|
|
|
if iface.MACAddress != "" {
|
|
|
|
res["mac_address"] = iface.MACAddress
|
|
|
|
}
|
|
|
|
if iface.Model != "" {
|
|
|
|
// Model can only be set on Shared networks
|
|
|
|
res["shared_network"] = true
|
|
|
|
}
|
|
|
|
if iface.NameServers != nil {
|
|
|
|
res["name_servers"] = iface.NameServers
|
|
|
|
}
|
|
|
|
if iface.Nat != nil {
|
|
|
|
res["nat"] = iface.Nat
|
|
|
|
}
|
|
|
|
if iface.SearchDomains != nil {
|
|
|
|
res["search_domains"] = iface.SearchDomains
|
|
|
|
}
|
|
|
|
if iface.SecLists != nil {
|
|
|
|
res["sec_lists"] = iface.SecLists
|
|
|
|
}
|
|
|
|
if iface.Vnic != "" {
|
|
|
|
res["vnic"] = iface.Vnic
|
|
|
|
// VNIC can only be set on an IP Network
|
|
|
|
res["shared_network"] = false
|
|
|
|
}
|
|
|
|
if iface.VnicSets != nil {
|
|
|
|
res["vnic_sets"] = iface.VnicSets
|
|
|
|
}
|
|
|
|
|
|
|
|
result = append(result, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.Set("networking_info", result)
|
|
|
|
}
|
2017-04-05 22:40:05 +02:00
|
|
|
|
|
|
|
// Flattens the returned slice of storage attachments to a map
|
|
|
|
func readStorageAttachments(d *schema.ResourceData, attachments []compute.StorageAttachment) error {
|
|
|
|
result := make([]map[string]interface{}, 0)
|
|
|
|
|
|
|
|
if attachments == nil || len(attachments) == 0 {
|
|
|
|
return d.Set("storage", nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, attachment := range attachments {
|
|
|
|
res := make(map[string]interface{})
|
|
|
|
res["index"] = attachment.Index
|
|
|
|
res["volume"] = attachment.StorageVolumeName
|
|
|
|
res["name"] = attachment.Name
|
|
|
|
result = append(result, res)
|
|
|
|
}
|
|
|
|
return d.Set("storage", result)
|
|
|
|
}
|