2014-10-29 22:52:36 +01:00
|
|
|
package openstack
|
|
|
|
|
|
|
|
import (
|
2015-02-10 17:01:08 +01:00
|
|
|
"bytes"
|
2015-03-20 23:55:42 +01:00
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/hex"
|
2014-10-29 22:52:36 +01:00
|
|
|
"fmt"
|
|
|
|
"log"
|
2015-09-12 22:21:55 +02:00
|
|
|
"os"
|
2014-10-29 22:52:36 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
"github.com/rackspace/gophercloud"
|
2015-02-01 10:10:42 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
|
2015-04-01 17:31:21 +02:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
2015-01-04 17:52:49 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
2015-05-05 22:52:46 +02:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints"
|
2015-01-05 20:16:33 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
|
2015-03-21 03:35:31 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
|
2015-02-10 17:01:08 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
|
2015-02-11 06:24:38 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
2015-02-10 06:20:23 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
2014-10-29 22:52:36 +01:00
|
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
2015-01-05 20:16:33 +01:00
|
|
|
"github.com/rackspace/gophercloud/pagination"
|
2014-10-29 22:52:36 +01:00
|
|
|
)
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceComputeInstanceV2() *schema.Resource {
|
2014-10-29 22:52:36 +01:00
|
|
|
return &schema.Resource{
|
2015-01-26 19:09:27 +01:00
|
|
|
Create: resourceComputeInstanceV2Create,
|
|
|
|
Read: resourceComputeInstanceV2Read,
|
|
|
|
Update: resourceComputeInstanceV2Update,
|
|
|
|
Delete: resourceComputeInstanceV2Delete,
|
2014-10-29 22:52:36 +01:00
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
2015-01-26 18:54:07 +01:00
|
|
|
"region": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
2015-04-11 06:11:34 +02:00
|
|
|
DefaultFunc: envDefaultFuncAllowMissing("OS_REGION_NAME"),
|
2015-01-26 18:54:07 +01:00
|
|
|
},
|
2014-10-29 22:52:36 +01:00
|
|
|
"name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: false,
|
|
|
|
},
|
2015-02-10 06:20:23 +01:00
|
|
|
"image_id": &schema.Schema{
|
2015-09-12 22:21:55 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Computed: true,
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
2015-02-10 06:20:23 +01:00
|
|
|
"image_name": &schema.Schema{
|
2015-09-12 22:21:55 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Computed: true,
|
2015-02-10 06:20:23 +01:00
|
|
|
},
|
2015-02-11 06:24:38 +01:00
|
|
|
"flavor_id": &schema.Schema{
|
2015-01-27 18:19:11 +01:00
|
|
|
Type: schema.TypeString,
|
2015-02-01 10:10:42 +01:00
|
|
|
Optional: true,
|
2015-01-27 18:19:11 +01:00
|
|
|
ForceNew: false,
|
2015-02-11 06:24:38 +01:00
|
|
|
Computed: true,
|
2015-01-27 06:35:18 +01:00
|
|
|
DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"),
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
2015-02-11 06:24:38 +01:00
|
|
|
"flavor_name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: false,
|
|
|
|
Computed: true,
|
|
|
|
DefaultFunc: envDefaultFunc("OS_FLAVOR_NAME"),
|
|
|
|
},
|
2015-02-06 14:34:11 +01:00
|
|
|
"floating_ip": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: false,
|
|
|
|
},
|
2015-04-01 17:31:21 +02:00
|
|
|
"user_data": &schema.Schema{
|
2015-03-20 23:55:42 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
// just stash the hash for state & diff comparisons
|
|
|
|
StateFunc: func(v interface{}) string {
|
|
|
|
switch v.(type) {
|
|
|
|
case string:
|
|
|
|
hash := sha1.Sum([]byte(v.(string)))
|
|
|
|
return hex.EncodeToString(hash[:])
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2014-10-29 22:52:36 +01:00
|
|
|
"security_groups": &schema.Schema{
|
2015-10-27 07:50:39 +01:00
|
|
|
Type: schema.TypeSet,
|
2014-10-29 22:52:36 +01:00
|
|
|
Optional: true,
|
|
|
|
ForceNew: false,
|
2015-11-07 05:01:50 +01:00
|
|
|
Computed: true,
|
2014-10-29 22:52:36 +01:00
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
2015-10-27 07:50:39 +01:00
|
|
|
Set: schema.HashString,
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
|
|
|
"availability_zone": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
2015-02-01 01:21:57 +01:00
|
|
|
"network": &schema.Schema{
|
2014-10-29 22:52:36 +01:00
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
2015-04-01 18:06:47 +02:00
|
|
|
Computed: true,
|
2014-10-29 22:52:36 +01:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"uuid": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2015-03-24 14:24:04 +01:00
|
|
|
Computed: true,
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
2015-03-21 03:35:31 +01:00
|
|
|
"name": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2015-03-24 14:24:04 +01:00
|
|
|
Computed: true,
|
2015-03-21 03:35:31 +01:00
|
|
|
},
|
2014-10-29 22:52:36 +01:00
|
|
|
"port": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2015-03-24 14:24:04 +01:00
|
|
|
Computed: true,
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
2015-03-21 03:16:46 +01:00
|
|
|
"fixed_ip_v4": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2015-03-24 14:24:04 +01:00
|
|
|
Computed: true,
|
2015-03-21 03:16:46 +01:00
|
|
|
},
|
|
|
|
"fixed_ip_v6": &schema.Schema{
|
2014-10-29 22:52:36 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2015-03-24 14:24:04 +01:00
|
|
|
Computed: true,
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
2015-03-21 04:12:22 +01:00
|
|
|
"mac": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
2015-03-24 14:24:04 +01:00
|
|
|
Computed: true,
|
2015-03-21 04:12:22 +01:00
|
|
|
},
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"metadata": &schema.Schema{
|
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
2015-01-04 17:52:49 +01:00
|
|
|
ForceNew: false,
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
|
|
|
"config_drive": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
2015-01-07 19:38:23 +01:00
|
|
|
"admin_pass": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: false,
|
|
|
|
},
|
2014-10-29 22:52:36 +01:00
|
|
|
"access_ip_v4": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: false,
|
|
|
|
},
|
|
|
|
"access_ip_v6": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: false,
|
|
|
|
},
|
2015-01-04 17:52:49 +01:00
|
|
|
"key_pair": &schema.Schema{
|
2015-01-07 19:38:23 +01:00
|
|
|
Type: schema.TypeString,
|
2015-01-04 17:52:49 +01:00
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
2015-02-01 10:10:42 +01:00
|
|
|
"block_device": &schema.Schema{
|
2015-09-12 19:56:38 +02:00
|
|
|
// TODO: This is a set because we don't support singleton
|
|
|
|
// sub-resources today. We'll enforce that the set only ever has
|
|
|
|
// length zero or one below. When TF gains support for
|
|
|
|
// sub-resources this can be converted.
|
|
|
|
// As referenced in resource_aws_instance.go
|
2015-09-12 06:15:21 +02:00
|
|
|
Type: schema.TypeSet,
|
2015-02-01 10:10:42 +01:00
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
2015-02-01 10:36:58 +01:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"uuid": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"source_type": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
"volume_size": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"destination_type": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
"boot_index": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2015-09-12 06:15:21 +02:00
|
|
|
"delete_on_termination": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
2015-02-01 10:36:58 +01:00
|
|
|
},
|
|
|
|
},
|
2015-09-12 06:15:21 +02:00
|
|
|
Set: func(v interface{}) int {
|
|
|
|
// there can only be one bootable block device; no need to hash anything
|
|
|
|
return 0
|
|
|
|
},
|
2015-02-01 10:10:42 +01:00
|
|
|
},
|
2015-02-10 17:01:08 +01:00
|
|
|
"volume": &schema.Schema{
|
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
2015-09-12 06:15:21 +02:00
|
|
|
Computed: true,
|
2015-02-10 17:01:08 +01:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"volume_id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
2015-09-12 06:15:21 +02:00
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
2015-02-10 17:01:08 +01:00
|
|
|
},
|
|
|
|
"device": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: resourceComputeVolumeAttachmentHash,
|
|
|
|
},
|
2015-05-05 22:52:46 +02:00
|
|
|
"scheduler_hints": &schema.Schema{
|
|
|
|
Type: schema.TypeSet,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"group": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
"different_host": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"same_host": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"query": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
|
|
},
|
|
|
|
"target_cell": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
"build_near_host_ip": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Set: resourceComputeSchedulerHintsHash,
|
|
|
|
},
|
2014-10-29 22:52:36 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error {
|
2014-10-29 22:52:36 +01:00
|
|
|
config := meta.(*Config)
|
2015-01-31 22:33:54 +01:00
|
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
2015-01-26 18:54:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-01-04 17:52:49 +01:00
|
|
|
var createOpts servers.CreateOptsBuilder
|
|
|
|
|
2015-09-12 22:21:55 +02:00
|
|
|
// Determines the Image ID using the following rules:
|
|
|
|
// If a bootable block_device was specified, ignore the image altogether.
|
|
|
|
// If an image_id was specified, use it.
|
|
|
|
// If an image_name was specified, look up the image ID, report if error.
|
|
|
|
imageId, err := getImageIDFromConfig(computeClient, d)
|
2015-02-10 06:20:23 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-11 06:24:38 +01:00
|
|
|
flavorId, err := getFlavorID(computeClient, d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-03-21 03:35:31 +01:00
|
|
|
networkDetails, err := resourceInstanceNetworks(computeClient, d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
// determine if volume/block_device configuration is correct
|
|
|
|
// this includes ensuring volume_ids are set
|
|
|
|
// and if only one block_device was specified.
|
|
|
|
if err := checkVolumeConfig(d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-03-21 03:35:31 +01:00
|
|
|
networks := make([]servers.Network, len(networkDetails))
|
|
|
|
for i, net := range networkDetails {
|
|
|
|
networks[i] = servers.Network{
|
|
|
|
UUID: net["uuid"].(string),
|
|
|
|
Port: net["port"].(string),
|
|
|
|
FixedIP: net["fixed_ip_v4"].(string),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-27 06:35:18 +01:00
|
|
|
createOpts = &servers.CreateOpts{
|
2015-01-05 20:16:33 +01:00
|
|
|
Name: d.Get("name").(string),
|
2015-02-10 06:20:23 +01:00
|
|
|
ImageRef: imageId,
|
2015-02-11 06:24:38 +01:00
|
|
|
FlavorRef: flavorId,
|
2015-01-26 19:09:27 +01:00
|
|
|
SecurityGroups: resourceInstanceSecGroupsV2(d),
|
2014-10-29 22:52:36 +01:00
|
|
|
AvailabilityZone: d.Get("availability_zone").(string),
|
2015-03-21 03:35:31 +01:00
|
|
|
Networks: networks,
|
2015-01-26 19:09:27 +01:00
|
|
|
Metadata: resourceInstanceMetadataV2(d),
|
2014-10-29 22:52:36 +01:00
|
|
|
ConfigDrive: d.Get("config_drive").(bool),
|
2015-01-07 19:38:23 +01:00
|
|
|
AdminPass: d.Get("admin_pass").(string),
|
2015-03-20 23:55:42 +01:00
|
|
|
UserData: []byte(d.Get("user_data").(string)),
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
|
|
|
|
2015-01-07 19:38:23 +01:00
|
|
|
if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" {
|
|
|
|
createOpts = &keypairs.CreateOptsExt{
|
2015-01-27 06:35:18 +01:00
|
|
|
createOpts,
|
2015-01-07 19:38:23 +01:00
|
|
|
keyName,
|
2015-01-04 17:52:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-12 06:15:21 +02:00
|
|
|
if v, ok := d.GetOk("block_device"); ok {
|
|
|
|
vL := v.(*schema.Set).List()
|
|
|
|
for _, v := range vL {
|
|
|
|
blockDeviceRaw := v.(map[string]interface{})
|
|
|
|
blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw)
|
|
|
|
createOpts = &bootfromvolume.CreateOptsExt{
|
|
|
|
createOpts,
|
|
|
|
blockDevice,
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] Create BFV Options: %+v", createOpts)
|
2015-02-01 10:10:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:52:46 +02:00
|
|
|
schedulerHintsRaw := d.Get("scheduler_hints").(*schema.Set).List()
|
|
|
|
if len(schedulerHintsRaw) > 0 {
|
|
|
|
log.Printf("[DEBUG] schedulerhints: %+v", schedulerHintsRaw)
|
|
|
|
schedulerHints := resourceInstanceSchedulerHintsV2(d, schedulerHintsRaw[0].(map[string]interface{}))
|
|
|
|
createOpts = &schedulerhints.CreateOptsExt{
|
|
|
|
createOpts,
|
|
|
|
schedulerHints,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-24 13:59:55 +01:00
|
|
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
2015-09-12 22:21:55 +02:00
|
|
|
|
|
|
|
// If a block_device is used, use the bootfromvolume.Create function as it allows an empty ImageRef.
|
|
|
|
// Otherwise, use the normal servers.Create function.
|
|
|
|
var server *servers.Server
|
|
|
|
if _, ok := d.GetOk("block_device"); ok {
|
|
|
|
server, err = bootfromvolume.Create(computeClient, createOpts).Extract()
|
|
|
|
} else {
|
|
|
|
server, err = servers.Create(computeClient, createOpts).Extract()
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:52:36 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating OpenStack server: %s", err)
|
|
|
|
}
|
|
|
|
log.Printf("[INFO] Instance ID: %s", server.ID)
|
|
|
|
|
|
|
|
// Store the ID now
|
|
|
|
d.SetId(server.ID)
|
|
|
|
|
|
|
|
// Wait for the instance to become running so we can get some attributes
|
|
|
|
// that aren't available until later.
|
|
|
|
log.Printf(
|
|
|
|
"[DEBUG] Waiting for instance (%s) to become running",
|
|
|
|
server.ID)
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"BUILD"},
|
|
|
|
Target: "ACTIVE",
|
2015-01-26 19:09:27 +01:00
|
|
|
Refresh: ServerV2StateRefreshFunc(computeClient, server.ID),
|
2015-12-08 23:03:33 +01:00
|
|
|
Timeout: 30 * time.Minute,
|
2014-10-29 22:52:36 +01:00
|
|
|
Delay: 10 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
|
2015-01-08 00:45:06 +01:00
|
|
|
_, err = stateConf.WaitForState()
|
2014-10-29 22:52:36 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for instance (%s) to become ready: %s",
|
|
|
|
server.ID, err)
|
|
|
|
}
|
2015-02-06 14:34:11 +01:00
|
|
|
floatingIP := d.Get("floating_ip").(string)
|
2015-02-09 13:27:30 +01:00
|
|
|
if floatingIP != "" {
|
2015-04-01 17:31:21 +02:00
|
|
|
if err := floatingip.Associate(computeClient, server.ID, floatingIP).ExtractErr(); err != nil {
|
|
|
|
return fmt.Errorf("Error associating floating IP: %s", err)
|
2015-02-06 14:34:11 +01:00
|
|
|
}
|
|
|
|
}
|
2014-10-29 22:52:36 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
// if volumes were specified, attach them after the instance has launched.
|
|
|
|
if v, ok := d.GetOk("volume"); ok {
|
2015-02-10 17:01:08 +01:00
|
|
|
vols := v.(*schema.Set).List()
|
2015-09-12 19:56:38 +02:00
|
|
|
if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
|
|
|
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
|
|
|
} else {
|
|
|
|
if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil {
|
|
|
|
return err
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
return resourceComputeInstanceV2Read(d, meta)
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error {
|
2014-10-29 22:52:36 +01:00
|
|
|
config := meta.(*Config)
|
2015-01-31 22:33:54 +01:00
|
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
2015-01-26 18:54:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
|
|
}
|
2014-10-29 22:52:36 +01:00
|
|
|
|
2015-01-26 18:54:07 +01:00
|
|
|
server, err := servers.Get(computeClient, d.Id()).Extract()
|
2014-10-29 22:52:36 +01:00
|
|
|
if err != nil {
|
2015-02-10 04:27:39 +01:00
|
|
|
return CheckDeleted(d, err, "server")
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server)
|
|
|
|
|
|
|
|
d.Set("name", server.Name)
|
2015-03-21 04:12:22 +01:00
|
|
|
|
|
|
|
// begin reading the network configuration
|
2014-10-29 22:52:36 +01:00
|
|
|
d.Set("access_ip_v4", server.AccessIPv4)
|
|
|
|
d.Set("access_ip_v6", server.AccessIPv6)
|
2015-02-10 06:47:35 +01:00
|
|
|
hostv4 := server.AccessIPv4
|
2015-03-21 04:12:22 +01:00
|
|
|
hostv6 := server.AccessIPv6
|
|
|
|
|
|
|
|
networkDetails, err := resourceInstanceNetworks(computeClient, d)
|
2015-03-24 14:24:04 +01:00
|
|
|
addresses := resourceInstanceAddresses(server.Addresses)
|
2015-03-21 04:12:22 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there are no networkDetails, make networks at least a length of 1
|
|
|
|
networkLength := 1
|
|
|
|
if len(networkDetails) > 0 {
|
|
|
|
networkLength = len(networkDetails)
|
|
|
|
}
|
|
|
|
networks := make([]map[string]interface{}, networkLength)
|
|
|
|
|
|
|
|
// Loop through all networks and addresses,
|
|
|
|
// merge relevant address details.
|
|
|
|
if len(networkDetails) == 0 {
|
2015-04-01 03:56:34 +02:00
|
|
|
for netName, n := range addresses {
|
2015-03-21 04:12:22 +01:00
|
|
|
if floatingIP, ok := n["floating_ip"]; ok {
|
|
|
|
hostv4 = floatingIP.(string)
|
|
|
|
} else {
|
|
|
|
if hostv4 == "" && n["fixed_ip_v4"] != nil {
|
|
|
|
hostv4 = n["fixed_ip_v4"].(string)
|
2015-02-04 22:14:36 +01:00
|
|
|
}
|
|
|
|
}
|
2015-03-21 04:12:22 +01:00
|
|
|
|
|
|
|
if hostv6 == "" && n["fixed_ip_v6"] != nil {
|
|
|
|
hostv6 = n["fixed_ip_v6"].(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
networks[0] = map[string]interface{}{
|
2015-04-01 03:56:34 +02:00
|
|
|
"name": netName,
|
2015-03-21 04:12:22 +01:00
|
|
|
"fixed_ip_v4": n["fixed_ip_v4"],
|
|
|
|
"fixed_ip_v6": n["fixed_ip_v6"],
|
|
|
|
"mac": n["mac"],
|
|
|
|
}
|
2015-02-04 22:14:36 +01:00
|
|
|
}
|
2015-03-21 04:12:22 +01:00
|
|
|
} else {
|
|
|
|
for i, net := range networkDetails {
|
|
|
|
n := addresses[net["name"].(string)]
|
2014-10-29 22:52:36 +01:00
|
|
|
|
2015-03-21 04:12:22 +01:00
|
|
|
if floatingIP, ok := n["floating_ip"]; ok {
|
|
|
|
hostv4 = floatingIP.(string)
|
|
|
|
} else {
|
|
|
|
if hostv4 == "" && n["fixed_ip_v4"] != nil {
|
|
|
|
hostv4 = n["fixed_ip_v4"].(string)
|
2015-02-10 06:47:35 +01:00
|
|
|
}
|
|
|
|
}
|
2015-03-21 04:12:22 +01:00
|
|
|
|
|
|
|
if hostv6 == "" && n["fixed_ip_v6"] != nil {
|
|
|
|
hostv6 = n["fixed_ip_v6"].(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
networks[i] = map[string]interface{}{
|
|
|
|
"uuid": networkDetails[i]["uuid"],
|
|
|
|
"name": networkDetails[i]["name"],
|
|
|
|
"port": networkDetails[i]["port"],
|
|
|
|
"fixed_ip_v4": n["fixed_ip_v4"],
|
|
|
|
"fixed_ip_v6": n["fixed_ip_v6"],
|
|
|
|
"mac": n["mac"],
|
|
|
|
}
|
2015-02-10 06:47:35 +01:00
|
|
|
}
|
|
|
|
}
|
2015-03-21 04:12:22 +01:00
|
|
|
|
2015-03-24 14:24:04 +01:00
|
|
|
log.Printf("[DEBUG] new networks: %+v", networks)
|
|
|
|
|
2015-03-21 04:12:22 +01:00
|
|
|
d.Set("network", networks)
|
|
|
|
d.Set("access_ip_v4", hostv4)
|
2015-02-10 06:47:35 +01:00
|
|
|
d.Set("access_ip_v6", hostv6)
|
2015-03-21 04:12:22 +01:00
|
|
|
log.Printf("hostv4: %s", hostv4)
|
2015-02-10 06:47:35 +01:00
|
|
|
log.Printf("hostv6: %s", hostv6)
|
2014-10-29 22:52:36 +01:00
|
|
|
|
2015-03-21 04:12:22 +01:00
|
|
|
// prefer the v6 address if no v4 address exists.
|
2015-02-10 06:47:35 +01:00
|
|
|
preferredv := ""
|
|
|
|
if hostv4 != "" {
|
|
|
|
preferredv = hostv4
|
|
|
|
} else if hostv6 != "" {
|
|
|
|
preferredv = hostv6
|
|
|
|
}
|
|
|
|
|
|
|
|
if preferredv != "" {
|
|
|
|
// Initialize the connection info
|
|
|
|
d.SetConnInfo(map[string]string{
|
|
|
|
"type": "ssh",
|
|
|
|
"host": preferredv,
|
|
|
|
})
|
|
|
|
}
|
2015-03-21 04:12:22 +01:00
|
|
|
// end network configuration
|
2014-10-29 22:52:36 +01:00
|
|
|
|
|
|
|
d.Set("metadata", server.Metadata)
|
2015-01-05 20:16:33 +01:00
|
|
|
|
2015-02-12 05:56:26 +01:00
|
|
|
secGrpNames := []string{}
|
2015-02-12 21:58:12 +01:00
|
|
|
for _, sg := range server.SecurityGroups {
|
|
|
|
secGrpNames = append(secGrpNames, sg["name"].(string))
|
|
|
|
}
|
2015-02-12 05:56:26 +01:00
|
|
|
d.Set("security_groups", secGrpNames)
|
2015-01-05 20:16:33 +01:00
|
|
|
|
2015-02-11 06:24:38 +01:00
|
|
|
flavorId, ok := server.Flavor["id"].(string)
|
2015-01-05 18:05:25 +01:00
|
|
|
if !ok {
|
2015-01-10 22:59:59 +01:00
|
|
|
return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor)
|
2015-01-05 18:05:25 +01:00
|
|
|
}
|
2015-02-11 06:24:38 +01:00
|
|
|
d.Set("flavor_id", flavorId)
|
|
|
|
|
|
|
|
flavor, err := flavors.Get(computeClient, flavorId).Extract()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Set("flavor_name", flavor.Name)
|
2014-10-29 22:52:36 +01:00
|
|
|
|
2015-09-12 22:21:55 +02:00
|
|
|
// Set the instance's image information appropriately
|
|
|
|
if err := setImageInformation(computeClient, server, d); err != nil {
|
2015-02-11 06:33:04 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-10 17:01:08 +01:00
|
|
|
// volume attachments
|
2015-09-12 19:56:38 +02:00
|
|
|
if err := getVolumeAttachments(computeClient, d); err != nil {
|
2015-02-10 17:01:08 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-10-29 22:52:36 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error {
|
2014-10-29 22:52:36 +01:00
|
|
|
config := meta.(*Config)
|
2015-01-31 22:33:54 +01:00
|
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
2015-01-26 18:54:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
|
|
}
|
2014-10-29 22:52:36 +01:00
|
|
|
|
|
|
|
var updateOpts servers.UpdateOpts
|
|
|
|
if d.HasChange("name") {
|
|
|
|
updateOpts.Name = d.Get("name").(string)
|
|
|
|
}
|
|
|
|
if d.HasChange("access_ip_v4") {
|
|
|
|
updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string)
|
|
|
|
}
|
|
|
|
if d.HasChange("access_ip_v6") {
|
|
|
|
updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string)
|
|
|
|
}
|
|
|
|
|
2015-01-26 23:45:05 +01:00
|
|
|
if updateOpts != (servers.UpdateOpts{}) {
|
|
|
|
_, err := servers.Update(computeClient, d.Id(), updateOpts).Extract()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error updating OpenStack server: %s", err)
|
|
|
|
}
|
2015-01-02 20:40:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if d.HasChange("metadata") {
|
|
|
|
var metadataOpts servers.MetadataOpts
|
|
|
|
metadataOpts = make(servers.MetadataOpts)
|
|
|
|
newMetadata := d.Get("metadata").(map[string]interface{})
|
|
|
|
for k, v := range newMetadata {
|
|
|
|
metadataOpts[k] = v.(string)
|
|
|
|
}
|
|
|
|
|
2015-01-26 18:54:07 +01:00
|
|
|
_, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract()
|
2015-01-02 20:40:42 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err)
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-05 20:16:33 +01:00
|
|
|
if d.HasChange("security_groups") {
|
|
|
|
oldSGRaw, newSGRaw := d.GetChange("security_groups")
|
2015-10-28 02:11:32 +01:00
|
|
|
oldSGSet := oldSGRaw.(*schema.Set)
|
|
|
|
newSGSet := newSGRaw.(*schema.Set)
|
2015-01-05 20:16:33 +01:00
|
|
|
secgroupsToAdd := newSGSet.Difference(oldSGSet)
|
|
|
|
secgroupsToRemove := oldSGSet.Difference(newSGSet)
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd)
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove)
|
|
|
|
|
|
|
|
for _, g := range secgroupsToRemove.List() {
|
2015-01-26 18:54:07 +01:00
|
|
|
err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr()
|
2015-01-05 20:16:33 +01:00
|
|
|
if err != nil {
|
2015-02-17 18:48:23 +01:00
|
|
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
2015-02-13 01:25:45 +01:00
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
if errCode.Actual == 404 {
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id())
|
2015-01-05 20:16:33 +01:00
|
|
|
}
|
|
|
|
}
|
2015-05-18 21:45:50 +02:00
|
|
|
for _, g := range secgroupsToAdd.List() {
|
|
|
|
err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g.(string), d.Id())
|
|
|
|
}
|
|
|
|
|
2015-01-05 20:16:33 +01:00
|
|
|
}
|
|
|
|
|
2015-01-07 19:38:23 +01:00
|
|
|
if d.HasChange("admin_pass") {
|
|
|
|
if newPwd, ok := d.Get("admin_pass").(string); ok {
|
2015-01-26 18:54:07 +01:00
|
|
|
err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr()
|
2015-01-07 19:38:23 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 14:34:11 +01:00
|
|
|
if d.HasChange("floating_ip") {
|
2015-04-01 17:31:21 +02:00
|
|
|
oldFIP, newFIP := d.GetChange("floating_ip")
|
|
|
|
log.Printf("[DEBUG] Old Floating IP: %v", oldFIP)
|
|
|
|
log.Printf("[DEBUG] New Floating IP: %v", newFIP)
|
|
|
|
if oldFIP.(string) != "" {
|
|
|
|
log.Printf("[DEBUG] Attemping to disassociate %s from %s", oldFIP, d.Id())
|
|
|
|
if err := floatingip.Disassociate(computeClient, d.Id(), oldFIP.(string)).ExtractErr(); err != nil {
|
|
|
|
return fmt.Errorf("Error disassociating Floating IP during update: %s", err)
|
2015-02-06 14:34:11 +01:00
|
|
|
}
|
2015-04-01 17:31:21 +02:00
|
|
|
}
|
2015-02-06 14:34:11 +01:00
|
|
|
|
2015-04-01 17:31:21 +02:00
|
|
|
if newFIP.(string) != "" {
|
|
|
|
log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id())
|
|
|
|
if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil {
|
|
|
|
return fmt.Errorf("Error associating Floating IP during update: %s", err)
|
2015-02-06 14:34:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-10 17:01:08 +01:00
|
|
|
if d.HasChange("volume") {
|
2015-09-12 19:56:38 +02:00
|
|
|
// ensure the volume configuration is correct
|
|
|
|
if err := checkVolumeConfig(d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-10 17:01:08 +01:00
|
|
|
// old attachments and new attachments
|
|
|
|
oldAttachments, newAttachments := d.GetChange("volume")
|
|
|
|
|
|
|
|
// for each old attachment, detach the volume
|
|
|
|
oldAttachmentSet := oldAttachments.(*schema.Set).List()
|
2015-09-12 19:56:38 +02:00
|
|
|
if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil {
|
2015-02-10 17:01:08 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// for each new attachment, attach the volume
|
|
|
|
newAttachmentSet := newAttachments.(*schema.Set).List()
|
2015-09-12 19:56:38 +02:00
|
|
|
if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil {
|
2015-02-10 17:01:08 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetPartial("volume")
|
|
|
|
}
|
|
|
|
|
2015-02-12 06:05:38 +01:00
|
|
|
if d.HasChange("flavor_id") || d.HasChange("flavor_name") {
|
|
|
|
flavorId, err := getFlavorID(computeClient, d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-01-05 18:05:25 +01:00
|
|
|
resizeOpts := &servers.ResizeOpts{
|
2015-02-12 06:05:38 +01:00
|
|
|
FlavorRef: flavorId,
|
2015-01-05 18:05:25 +01:00
|
|
|
}
|
2015-03-24 13:59:55 +01:00
|
|
|
log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts)
|
2015-02-12 06:05:38 +01:00
|
|
|
err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr()
|
2015-01-05 18:05:25 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error resizing OpenStack server: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the instance to finish resizing.
|
|
|
|
log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id())
|
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"RESIZE"},
|
|
|
|
Target: "VERIFY_RESIZE",
|
2015-01-26 19:09:27 +01:00
|
|
|
Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()),
|
2015-01-05 18:05:25 +01:00
|
|
|
Timeout: 3 * time.Minute,
|
|
|
|
Delay: 10 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = stateConf.WaitForState()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm resize.
|
|
|
|
log.Printf("[DEBUG] Confirming resize")
|
2015-01-26 18:54:07 +01:00
|
|
|
err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr()
|
2015-01-05 18:05:25 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error confirming resize of OpenStack server: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stateConf = &resource.StateChangeConf{
|
|
|
|
Pending: []string{"VERIFY_RESIZE"},
|
|
|
|
Target: "ACTIVE",
|
2015-01-26 19:09:27 +01:00
|
|
|
Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()),
|
2015-01-05 18:05:25 +01:00
|
|
|
Timeout: 3 * time.Minute,
|
|
|
|
Delay: 10 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = stateConf.WaitForState()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
return resourceComputeInstanceV2Read(d, meta)
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error {
|
2014-10-29 22:52:36 +01:00
|
|
|
config := meta.(*Config)
|
2015-01-31 22:33:54 +01:00
|
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
2015-01-26 18:54:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
|
|
}
|
2014-10-29 22:52:36 +01:00
|
|
|
|
2015-01-26 18:54:07 +01:00
|
|
|
err = servers.Delete(computeClient, d.Id()).ExtractErr()
|
2014-10-29 22:52:36 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error deleting OpenStack server: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the instance to delete before moving on.
|
2015-01-05 18:05:25 +01:00
|
|
|
log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id())
|
2014-10-29 22:52:36 +01:00
|
|
|
|
|
|
|
stateConf := &resource.StateChangeConf{
|
2015-02-04 22:15:11 +01:00
|
|
|
Pending: []string{"ACTIVE"},
|
2015-02-04 04:30:10 +01:00
|
|
|
Target: "DELETED",
|
2015-01-26 19:09:27 +01:00
|
|
|
Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()),
|
2015-12-08 23:03:33 +01:00
|
|
|
Timeout: 30 * time.Minute,
|
2014-10-29 22:52:36 +01:00
|
|
|
Delay: 10 * time.Second,
|
|
|
|
MinTimeout: 3 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = stateConf.WaitForState()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error waiting for instance (%s) to delete: %s",
|
|
|
|
d.Id(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-01 10:10:42 +01:00
|
|
|
// ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
2014-10-29 22:52:36 +01:00
|
|
|
// an OpenStack instance.
|
2015-01-26 19:09:27 +01:00
|
|
|
func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc {
|
2014-10-29 22:52:36 +01:00
|
|
|
return func() (interface{}, string, error) {
|
|
|
|
s, err := servers.Get(client, instanceID).Extract()
|
|
|
|
if err != nil {
|
2015-02-17 18:48:23 +01:00
|
|
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
2015-02-04 04:30:10 +01:00
|
|
|
if !ok {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if errCode.Actual == 404 {
|
|
|
|
return s, "DELETED", nil
|
|
|
|
}
|
2014-10-29 22:52:36 +01:00
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, s.Status, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string {
|
2015-10-27 07:50:39 +01:00
|
|
|
rawSecGroups := d.Get("security_groups").(*schema.Set).List()
|
2015-02-12 06:29:31 +01:00
|
|
|
secgroups := make([]string, len(rawSecGroups))
|
|
|
|
for i, raw := range rawSecGroups {
|
2015-01-05 20:16:33 +01:00
|
|
|
secgroups[i] = raw.(string)
|
|
|
|
}
|
|
|
|
return secgroups
|
|
|
|
}
|
|
|
|
|
2015-03-21 03:35:31 +01:00
|
|
|
func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) {
|
2015-02-01 01:21:57 +01:00
|
|
|
rawNetworks := d.Get("network").([]interface{})
|
2015-06-24 01:19:42 +02:00
|
|
|
newNetworks := make([]map[string]interface{}, 0, len(rawNetworks))
|
2015-03-21 03:35:31 +01:00
|
|
|
var tenantnet tenantnetworks.Network
|
|
|
|
|
2015-04-01 23:31:55 +02:00
|
|
|
tenantNetworkExt := true
|
2015-06-24 01:19:42 +02:00
|
|
|
for _, raw := range rawNetworks {
|
|
|
|
// Not sure what causes this, but it is a possibility (see GH-2323).
|
|
|
|
// Since we call this function to reconcile what we'll save in the
|
|
|
|
// state anyways, we just ignore it.
|
|
|
|
if raw == nil {
|
|
|
|
continue
|
|
|
|
}
|
2015-03-21 03:35:31 +01:00
|
|
|
|
2015-06-24 01:19:42 +02:00
|
|
|
rawMap := raw.(map[string]interface{})
|
2015-03-21 03:35:31 +01:00
|
|
|
allPages, err := tenantnetworks.List(computeClient).AllPages()
|
|
|
|
if err != nil {
|
2015-04-01 23:31:55 +02:00
|
|
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
|
|
|
if !ok {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-21 03:35:31 +01:00
|
|
|
|
2015-04-01 23:31:55 +02:00
|
|
|
if errCode.Actual == 404 {
|
|
|
|
tenantNetworkExt = false
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-21 03:35:31 +01:00
|
|
|
}
|
|
|
|
|
2015-04-01 23:31:55 +02:00
|
|
|
networkID := ""
|
|
|
|
networkName := ""
|
|
|
|
if tenantNetworkExt {
|
|
|
|
networkList, err := tenantnetworks.ExtractNetworks(allPages)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-03-21 03:35:31 +01:00
|
|
|
}
|
2015-04-01 23:31:55 +02:00
|
|
|
|
|
|
|
for _, network := range networkList {
|
|
|
|
if network.Name == rawMap["name"] {
|
|
|
|
tenantnet = network
|
|
|
|
}
|
|
|
|
if network.ID == rawMap["uuid"] {
|
|
|
|
tenantnet = network
|
|
|
|
}
|
2015-03-21 03:35:31 +01:00
|
|
|
}
|
2015-04-01 23:31:55 +02:00
|
|
|
|
|
|
|
networkID = tenantnet.ID
|
|
|
|
networkName = tenantnet.Name
|
|
|
|
} else {
|
|
|
|
networkID = rawMap["uuid"].(string)
|
|
|
|
networkName = rawMap["name"].(string)
|
2015-03-21 03:35:31 +01:00
|
|
|
}
|
|
|
|
|
2015-06-24 01:19:42 +02:00
|
|
|
newNetworks = append(newNetworks, map[string]interface{}{
|
2015-04-01 23:31:55 +02:00
|
|
|
"uuid": networkID,
|
|
|
|
"name": networkName,
|
2015-03-21 03:35:31 +01:00
|
|
|
"port": rawMap["port"].(string),
|
|
|
|
"fixed_ip_v4": rawMap["fixed_ip_v4"].(string),
|
2015-06-24 01:19:42 +02:00
|
|
|
})
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
2015-03-21 03:35:31 +01:00
|
|
|
|
2015-03-21 04:12:22 +01:00
|
|
|
log.Printf("[DEBUG] networks: %+v", newNetworks)
|
2015-03-21 03:35:31 +01:00
|
|
|
return newNetworks, nil
|
2014-10-29 22:52:36 +01:00
|
|
|
}
|
|
|
|
|
2015-03-21 04:12:22 +01:00
|
|
|
func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} {
|
|
|
|
|
|
|
|
addrs := make(map[string]map[string]interface{})
|
|
|
|
for n, networkAddresses := range addresses {
|
|
|
|
addrs[n] = make(map[string]interface{})
|
|
|
|
for _, element := range networkAddresses.([]interface{}) {
|
|
|
|
address := element.(map[string]interface{})
|
|
|
|
if address["OS-EXT-IPS:type"] == "floating" {
|
|
|
|
addrs[n]["floating_ip"] = address["addr"]
|
|
|
|
} else {
|
|
|
|
if address["version"].(float64) == 4 {
|
|
|
|
addrs[n]["fixed_ip_v4"] = address["addr"].(string)
|
|
|
|
} else {
|
|
|
|
addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string))
|
|
|
|
}
|
|
|
|
}
|
2015-03-25 17:47:19 +01:00
|
|
|
if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok {
|
|
|
|
addrs[n]["mac"] = mac.(string)
|
|
|
|
}
|
2015-03-21 04:12:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Addresses: %+v", addresses)
|
|
|
|
|
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
2015-01-26 19:09:27 +01:00
|
|
|
func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
|
2014-10-29 22:52:36 +01:00
|
|
|
m := make(map[string]string)
|
|
|
|
for key, val := range d.Get("metadata").(map[string]interface{}) {
|
|
|
|
m[key] = val.(string)
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
2015-02-01 10:10:42 +01:00
|
|
|
|
|
|
|
func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice {
|
|
|
|
sourceType := bootfromvolume.SourceType(bd["source_type"].(string))
|
|
|
|
bfvOpts := []bootfromvolume.BlockDevice{
|
|
|
|
bootfromvolume.BlockDevice{
|
2015-09-12 06:15:21 +02:00
|
|
|
UUID: bd["uuid"].(string),
|
|
|
|
SourceType: sourceType,
|
|
|
|
VolumeSize: bd["volume_size"].(int),
|
|
|
|
DestinationType: bd["destination_type"].(string),
|
|
|
|
BootIndex: bd["boot_index"].(int),
|
|
|
|
DeleteOnTermination: bd["delete_on_termination"].(bool),
|
2015-02-01 10:10:42 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return bfvOpts
|
|
|
|
}
|
2015-02-06 14:34:11 +01:00
|
|
|
|
2015-05-05 22:52:46 +02:00
|
|
|
func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints {
|
|
|
|
differentHost := []string{}
|
|
|
|
if len(schedulerHintsRaw["different_host"].([]interface{})) > 0 {
|
|
|
|
for _, dh := range schedulerHintsRaw["different_host"].([]interface{}) {
|
|
|
|
differentHost = append(differentHost, dh.(string))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sameHost := []string{}
|
|
|
|
if len(schedulerHintsRaw["same_host"].([]interface{})) > 0 {
|
|
|
|
for _, sh := range schedulerHintsRaw["same_host"].([]interface{}) {
|
|
|
|
sameHost = append(sameHost, sh.(string))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query := make([]interface{}, len(schedulerHintsRaw["query"].([]interface{})))
|
|
|
|
if len(schedulerHintsRaw["query"].([]interface{})) > 0 {
|
|
|
|
for _, q := range schedulerHintsRaw["query"].([]interface{}) {
|
|
|
|
query = append(query, q.(string))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
schedulerHints := schedulerhints.SchedulerHints{
|
|
|
|
Group: schedulerHintsRaw["group"].(string),
|
|
|
|
DifferentHost: differentHost,
|
|
|
|
SameHost: sameHost,
|
|
|
|
Query: query,
|
|
|
|
TargetCell: schedulerHintsRaw["target_cell"].(string),
|
|
|
|
BuildNearHostIP: schedulerHintsRaw["build_near_host_ip"].(string),
|
|
|
|
}
|
|
|
|
|
|
|
|
return schedulerHints
|
|
|
|
}
|
|
|
|
|
2015-09-12 22:21:55 +02:00
|
|
|
func getImageIDFromConfig(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
|
|
|
|
// If block_device was used, an Image does not need to be specified.
|
|
|
|
// If an Image was specified, ignore it
|
|
|
|
if _, ok := d.GetOk("block_device"); ok {
|
|
|
|
return "", nil
|
|
|
|
}
|
2015-02-10 06:20:23 +01:00
|
|
|
|
2015-09-12 22:21:55 +02:00
|
|
|
if imageId := d.Get("image_id").(string); imageId != "" {
|
2015-02-11 05:11:04 +01:00
|
|
|
return imageId, nil
|
2015-09-12 22:21:55 +02:00
|
|
|
} else {
|
|
|
|
// try the OS_IMAGE_ID environment variable
|
|
|
|
if v := os.Getenv("OS_IMAGE_ID"); v != "" {
|
|
|
|
return v, nil
|
|
|
|
}
|
2015-02-11 05:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
imageName := d.Get("image_name").(string)
|
2015-09-12 22:21:55 +02:00
|
|
|
if imageName == "" {
|
|
|
|
// try the OS_IMAGE_NAME environment variable
|
|
|
|
if v := os.Getenv("OS_IMAGE_NAME"); v != "" {
|
|
|
|
imageName = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-11 05:11:04 +01:00
|
|
|
if imageName != "" {
|
2015-09-12 22:21:55 +02:00
|
|
|
imageId, err := images.IDFromName(computeClient, imageName)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return imageId, nil
|
|
|
|
}
|
2015-02-10 06:20:23 +01:00
|
|
|
|
2015-09-12 22:21:55 +02:00
|
|
|
return "", fmt.Errorf("Neither a boot device, image ID, or image name were able to be determined.")
|
|
|
|
}
|
2015-02-10 06:20:23 +01:00
|
|
|
|
2015-09-12 22:21:55 +02:00
|
|
|
func setImageInformation(computeClient *gophercloud.ServiceClient, server *servers.Server, d *schema.ResourceData) error {
|
|
|
|
// If block_device was used, an Image does not need to be specified.
|
|
|
|
// If an Image was specified, ignore it
|
|
|
|
if _, ok := d.GetOk("block_device"); ok {
|
|
|
|
d.Set("image_id", "Attempt to boot from volume - no image supplied")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
imageId := server.Image["id"].(string)
|
|
|
|
if imageId != "" {
|
|
|
|
d.Set("image_id", imageId)
|
|
|
|
if image, err := images.Get(computeClient, imageId).Extract(); err != nil {
|
|
|
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
|
|
|
if !ok {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if errCode.Actual == 404 {
|
|
|
|
// If the image name can't be found, set the value to "Image not found".
|
|
|
|
// The most likely scenario is that the image no longer exists in the Image Service
|
|
|
|
// but the instance still has a record from when it existed.
|
|
|
|
d.Set("image_name", "Image not found")
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
d.Set("image_name", image.Name)
|
2015-02-10 06:20:23 +01:00
|
|
|
}
|
|
|
|
}
|
2015-09-12 22:21:55 +02:00
|
|
|
|
|
|
|
return nil
|
2015-02-10 06:20:23 +01:00
|
|
|
}
|
2015-02-11 06:24:38 +01:00
|
|
|
|
|
|
|
func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
|
|
|
|
flavorId := d.Get("flavor_id").(string)
|
|
|
|
|
|
|
|
if flavorId != "" {
|
|
|
|
return flavorId, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
flavorCount := 0
|
|
|
|
flavorName := d.Get("flavor_name").(string)
|
|
|
|
if flavorName != "" {
|
|
|
|
pager := flavors.ListDetail(client, nil)
|
|
|
|
pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
|
|
flavorList, err := flavors.ExtractFlavors(page)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range flavorList {
|
|
|
|
if f.Name == flavorName {
|
|
|
|
flavorCount++
|
|
|
|
flavorId = f.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
switch flavorCount {
|
|
|
|
case 0:
|
|
|
|
return "", fmt.Errorf("Unable to find flavor: %s", flavorName)
|
|
|
|
case 1:
|
|
|
|
return flavorId, nil
|
|
|
|
default:
|
|
|
|
return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName)
|
|
|
|
}
|
|
|
|
}
|
2015-02-11 18:08:23 +01:00
|
|
|
return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.")
|
2015-02-11 06:24:38 +01:00
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
|
|
|
func resourceComputeVolumeAttachmentHash(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string)))
|
2015-09-12 06:15:21 +02:00
|
|
|
|
2015-02-10 17:01:08 +01:00
|
|
|
return hashcode.String(buf.String())
|
|
|
|
}
|
|
|
|
|
2015-05-05 22:52:46 +02:00
|
|
|
func resourceComputeSchedulerHintsHash(v interface{}) int {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
|
|
|
|
if m["group"] != nil {
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["group"].(string)))
|
|
|
|
}
|
|
|
|
|
|
|
|
if m["target_cell"] != nil {
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["target_cell"].(string)))
|
|
|
|
}
|
|
|
|
|
|
|
|
if m["build_host_near_ip"] != nil {
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["build_host_near_ip"].(string)))
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["different_host"].([]interface{})))
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["same_host"].([]interface{})))
|
|
|
|
buf.WriteString(fmt.Sprintf("%s-", m["query"].([]interface{})))
|
|
|
|
|
|
|
|
return hashcode.String(buf.String())
|
|
|
|
}
|
|
|
|
|
2015-02-10 17:01:08 +01:00
|
|
|
func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error {
|
2015-09-12 19:56:38 +02:00
|
|
|
for _, v := range vols {
|
|
|
|
va := v.(map[string]interface{})
|
|
|
|
volumeId := va["volume_id"].(string)
|
|
|
|
device := va["device"].(string)
|
|
|
|
|
|
|
|
s := ""
|
|
|
|
if serverId != "" {
|
|
|
|
s = serverId
|
|
|
|
} else if va["server_id"] != "" {
|
|
|
|
s = va["server_id"].(string)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Unable to determine server ID to attach volume.")
|
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
vaOpts := &volumeattach.CreateOpts{
|
|
|
|
Device: device,
|
|
|
|
VolumeID: volumeId,
|
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"attaching", "available"},
|
|
|
|
Target: "in-use",
|
|
|
|
Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)),
|
|
|
|
Timeout: 30 * time.Minute,
|
|
|
|
Delay: 5 * time.Second,
|
|
|
|
MinTimeout: 2 * time.Second,
|
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
if _, err := stateConf.WaitForState(); err != nil {
|
|
|
|
return err
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|
2015-09-12 19:56:38 +02:00
|
|
|
|
|
|
|
log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId)
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error {
|
2015-09-12 19:56:38 +02:00
|
|
|
for _, v := range vols {
|
|
|
|
va := v.(map[string]interface{})
|
|
|
|
aId := va["id"].(string)
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
stateConf := &resource.StateChangeConf{
|
|
|
|
Pending: []string{"detaching", "in-use"},
|
|
|
|
Target: "available",
|
|
|
|
Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)),
|
|
|
|
Timeout: 30 * time.Minute,
|
|
|
|
Delay: 5 * time.Second,
|
|
|
|
MinTimeout: 2 * time.Second,
|
|
|
|
}
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
if _, err := stateConf.WaitForState(); err != nil {
|
|
|
|
return err
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|
2015-09-12 19:56:38 +02:00
|
|
|
log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId)
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
func getVolumeAttachments(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error {
|
2015-02-10 17:01:08 +01:00
|
|
|
var attachments []volumeattach.VolumeAttachment
|
2015-09-12 19:56:38 +02:00
|
|
|
|
|
|
|
err := volumeattach.List(computeClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) {
|
2015-02-10 17:01:08 +01:00
|
|
|
actual, err := volumeattach.ExtractVolumeAttachments(page)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
attachments = actual
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2015-09-12 19:56:38 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
vols := make([]map[string]interface{}, len(attachments))
|
|
|
|
for i, attachment := range attachments {
|
|
|
|
vols[i] = make(map[string]interface{})
|
|
|
|
vols[i]["id"] = attachment.ID
|
|
|
|
vols[i]["volume_id"] = attachment.VolumeID
|
|
|
|
vols[i]["device"] = attachment.Device
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|
2015-09-12 19:56:38 +02:00
|
|
|
log.Printf("[INFO] Volume attachments: %v", vols)
|
|
|
|
d.Set("volume", vols)
|
2015-02-10 17:01:08 +01:00
|
|
|
|
2015-09-12 19:56:38 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkVolumeConfig(d *schema.ResourceData) error {
|
|
|
|
// Although a volume_id is required to attach a volume, in order to be able to report
|
|
|
|
// the attached volumes of an instance, it must be "computed" and thus "optional".
|
|
|
|
// This accounts for situations such as "boot from volume" as well as volumes being
|
|
|
|
// attached to the instance outside of Terraform.
|
|
|
|
if v := d.Get("volume"); v != nil {
|
|
|
|
vols := v.(*schema.Set).List()
|
|
|
|
if len(vols) > 0 {
|
|
|
|
for _, v := range vols {
|
|
|
|
va := v.(map[string]interface{})
|
|
|
|
if va["volume_id"].(string) == "" {
|
|
|
|
return fmt.Errorf("A volume_id must be specified when attaching volumes.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("block_device"); ok {
|
|
|
|
vL := v.(*schema.Set).List()
|
|
|
|
if len(vL) > 1 {
|
|
|
|
return fmt.Errorf("Can only specify one block device to boot from.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2015-02-10 17:01:08 +01:00
|
|
|
}
|