Merge pull request #9407 from jtopjian/gophercloud-migration

provider/openstack: Gophercloud Migration
This commit is contained in:
Joe Topjian 2016-10-22 20:45:29 -06:00 committed by GitHub
commit a148180505
220 changed files with 15881 additions and 802 deletions

View File

@ -7,8 +7,8 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack"
) )
type Config struct { type Config struct {
@ -16,7 +16,6 @@ type Config struct {
UserID string UserID string
Password string Password string
Token string Token string
APIKey string
IdentityEndpoint string IdentityEndpoint string
TenantID string TenantID string
TenantName string TenantName string
@ -45,7 +44,6 @@ func (c *Config) loadAndValidate() error {
UserID: c.UserID, UserID: c.UserID,
Password: c.Password, Password: c.Password,
TokenID: c.Token, TokenID: c.Token,
APIKey: c.APIKey,
IdentityEndpoint: c.IdentityEndpoint, IdentityEndpoint: c.IdentityEndpoint,
TenantID: c.TenantID, TenantID: c.TenantID,
TenantName: c.TenantName, TenantName: c.TenantName,

View File

@ -48,11 +48,6 @@ func Provider() terraform.ResourceProvider {
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_TOKEN", ""), DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_TOKEN", ""),
}, },
"api_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OS_API_KEY", ""),
},
"domain_id": &schema.Schema{ "domain_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
@ -133,7 +128,6 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) {
UserID: d.Get("user_id").(string), UserID: d.Get("user_id").(string),
Password: d.Get("password").(string), Password: d.Get("password").(string),
Token: d.Get("token").(string), Token: d.Get("token").(string),
APIKey: d.Get("api_key").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
TenantName: d.Get("tenant_name").(string), TenantName: d.Get("tenant_name").(string),
DomainID: d.Get("domain_id").(string), DomainID: d.Get("domain_id").(string),

View File

@ -6,12 +6,12 @@ import (
"log" "log"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
) )
func resourceBlockStorageVolumeV1() *schema.Resource { func resourceBlockStorageVolumeV1() *schema.Resource {
@ -169,7 +169,7 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{})
return CheckDeleted(d, err, "volume") return CheckDeleted(d, err, "volume")
} }
log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v) log.Printf("[DEBUG] Retrieved volume %s: %+v", d.Id(), v)
d.Set("size", v.Size) d.Set("size", v.Size)
d.Set("description", v.Description) d.Set("description", v.Description)
@ -306,11 +306,7 @@ func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
v, err := volumes.Get(client, volumeID).Extract() v, err := volumes.Get(client, volumeID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return nil, "", err
}
if errCode.Actual == 404 {
return v, "deleted", nil return v, "deleted", nil
} }
return nil, "", err return nil, "", err

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
) )
func TestAccBlockStorageV1Volume_basic(t *testing.T) { func TestAccBlockStorageV1Volume_basic(t *testing.T) {
@ -117,13 +117,10 @@ func testAccCheckBlockStorageV1VolumeDoesNotExist(t *testing.T, n string, volume
_, err = volumes.Get(blockStorageClient, volume.ID).Extract() _, err = volumes.Get(blockStorageClient, volume.ID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return err
}
if errCode.Actual == 404 {
return nil return nil
} }
return err return err
} }

View File

@ -6,12 +6,12 @@ import (
"log" "log"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
) )
func resourceBlockStorageVolumeV2() *schema.Resource { func resourceBlockStorageVolumeV2() *schema.Resource {
@ -181,7 +181,7 @@ func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{})
return CheckDeleted(d, err, "volume") return CheckDeleted(d, err, "volume")
} }
log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v) log.Printf("[DEBUG] Retrieved volume %s: %+v", d.Id(), v)
d.Set("size", v.Size) d.Set("size", v.Size)
d.Set("description", v.Description) d.Set("description", v.Description)
@ -195,9 +195,9 @@ func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{})
attachments := make([]map[string]interface{}, len(v.Attachments)) attachments := make([]map[string]interface{}, len(v.Attachments))
for i, attachment := range v.Attachments { for i, attachment := range v.Attachments {
attachments[i] = make(map[string]interface{}) attachments[i] = make(map[string]interface{})
attachments[i]["id"] = attachment["id"] attachments[i]["id"] = attachment.ID
attachments[i]["instance_id"] = attachment["server_id"] attachments[i]["instance_id"] = attachment.ServerID
attachments[i]["device"] = attachment["device"] attachments[i]["device"] = attachment.Device
log.Printf("[DEBUG] attachment: %v", attachment) log.Printf("[DEBUG] attachment: %v", attachment)
} }
d.Set("attachment", attachments) d.Set("attachment", attachments)
@ -249,7 +249,7 @@ func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{}
} else { } else {
for _, volumeAttachment := range v.Attachments { for _, volumeAttachment := range v.Attachments {
log.Printf("[DEBUG] Attachment: %v", volumeAttachment) log.Printf("[DEBUG] Attachment: %v", volumeAttachment)
if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil { if err := volumeattach.Delete(computeClient, volumeAttachment.ServerID, volumeAttachment.ID).ExtractErr(); err != nil {
return err return err
} }
} }
@ -318,11 +318,7 @@ func VolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
v, err := volumes.Get(client, volumeID).Extract() v, err := volumes.Get(client, volumeID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return nil, "", err
}
if errCode.Actual == 404 {
return v, "deleted", nil return v, "deleted", nil
} }
return nil, "", err return nil, "", err

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes" "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
) )
func TestAccBlockStorageV2Volume_basic(t *testing.T) { func TestAccBlockStorageV2Volume_basic(t *testing.T) {
@ -130,11 +130,7 @@ func testAccCheckBlockStorageV2VolumeDoesNotExist(t *testing.T, n string, volume
_, err = volumes.Get(blockStorageClient, volume.ID).Extract() _, err = volumes.Get(blockStorageClient, volume.ID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return err
}
if errCode.Actual == 404 {
return nil return nil
} }
return err return err

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
) )
func resourceComputeFloatingIPV2() *schema.Resource { func resourceComputeFloatingIPV2() *schema.Resource {
@ -58,11 +58,11 @@ func resourceComputeFloatingIPV2Create(d *schema.ResourceData, meta interface{})
return fmt.Errorf("Error creating OpenStack compute client: %s", err) return fmt.Errorf("Error creating OpenStack compute client: %s", err)
} }
createOpts := &floatingip.CreateOpts{ createOpts := &floatingips.CreateOpts{
Pool: d.Get("pool").(string), Pool: d.Get("pool").(string),
} }
log.Printf("[DEBUG] Create Options: %#v", createOpts) log.Printf("[DEBUG] Create Options: %#v", createOpts)
newFip, err := floatingip.Create(computeClient, createOpts).Extract() newFip, err := floatingips.Create(computeClient, createOpts).Extract()
if err != nil { if err != nil {
return fmt.Errorf("Error creating Floating IP: %s", err) return fmt.Errorf("Error creating Floating IP: %s", err)
} }
@ -79,7 +79,7 @@ func resourceComputeFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e
return fmt.Errorf("Error creating OpenStack compute client: %s", err) return fmt.Errorf("Error creating OpenStack compute client: %s", err)
} }
fip, err := floatingip.Get(computeClient, d.Id()).Extract() fip, err := floatingips.Get(computeClient, d.Id()).Extract()
if err != nil { if err != nil {
return CheckDeleted(d, err, "floating ip") return CheckDeleted(d, err, "floating ip")
} }
@ -102,7 +102,7 @@ func resourceComputeFloatingIPV2Delete(d *schema.ResourceData, meta interface{})
} }
log.Printf("[DEBUG] Deleting Floating IP %s", d.Id()) log.Printf("[DEBUG] Deleting Floating IP %s", d.Id())
if err := floatingip.Delete(computeClient, d.Id()).ExtractErr(); err != nil { if err := floatingips.Delete(computeClient, d.Id()).ExtractErr(); err != nil {
return fmt.Errorf("Error deleting Floating IP: %s", err) return fmt.Errorf("Error deleting Floating IP: %s", err)
} }

View File

@ -8,12 +8,12 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
) )
func TestAccComputeV2FloatingIP_basic(t *testing.T) { func TestAccComputeV2FloatingIP_basic(t *testing.T) {
var floatingIP floatingip.FloatingIP var floatingIP floatingips.FloatingIP
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -32,7 +32,7 @@ func TestAccComputeV2FloatingIP_basic(t *testing.T) {
func TestAccComputeV2FloatingIP_attach(t *testing.T) { func TestAccComputeV2FloatingIP_attach(t *testing.T) {
var instance servers.Server var instance servers.Server
var fip floatingip.FloatingIP var fip floatingips.FloatingIP
var testAccComputeV2FloatingIP_attach = fmt.Sprintf(` var testAccComputeV2FloatingIP_attach = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip" { resource "openstack_compute_floatingip_v2" "myip" {
} }
@ -77,7 +77,7 @@ func testAccCheckComputeV2FloatingIPDestroy(s *terraform.State) error {
continue continue
} }
_, err := floatingip.Get(computeClient, rs.Primary.ID).Extract() _, err := floatingips.Get(computeClient, rs.Primary.ID).Extract()
if err == nil { if err == nil {
return fmt.Errorf("FloatingIP still exists") return fmt.Errorf("FloatingIP still exists")
} }
@ -86,7 +86,7 @@ func testAccCheckComputeV2FloatingIPDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingip.FloatingIP) resource.TestCheckFunc { func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingips.FloatingIP) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -103,7 +103,7 @@ func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingi
return fmt.Errorf("(testAccCheckComputeV2FloatingIPExists) Error creating OpenStack compute client: %s", err) return fmt.Errorf("(testAccCheckComputeV2FloatingIPExists) Error creating OpenStack compute client: %s", err)
} }
found, err := floatingip.Get(computeClient, rs.Primary.ID).Extract() found, err := floatingips.Get(computeClient, rs.Primary.ID).Extract()
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,21 +9,21 @@ import (
"os" "os"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
) )
func resourceComputeInstanceV2() *schema.Resource { func resourceComputeInstanceV2() *schema.Resource {
@ -376,6 +376,8 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
} }
} }
configDrive := d.Get("config_drive").(bool)
createOpts = &servers.CreateOpts{ createOpts = &servers.CreateOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
ImageRef: imageId, ImageRef: imageId,
@ -384,7 +386,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
AvailabilityZone: d.Get("availability_zone").(string), AvailabilityZone: d.Get("availability_zone").(string),
Networks: networks, Networks: networks,
Metadata: resourceInstanceMetadataV2(d), Metadata: resourceInstanceMetadataV2(d),
ConfigDrive: d.Get("config_drive").(bool), ConfigDrive: &configDrive,
AdminPass: d.Get("admin_pass").(string), AdminPass: d.Get("admin_pass").(string),
UserData: []byte(d.Get("user_data").(string)), UserData: []byte(d.Get("user_data").(string)),
Personality: resourceInstancePersonalityV2(d), Personality: resourceInstancePersonalityV2(d),
@ -398,7 +400,11 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
} }
if vL, ok := d.GetOk("block_device"); ok { if vL, ok := d.GetOk("block_device"); ok {
blockDevices := resourceInstanceBlockDevicesV2(d, vL.([]interface{})) blockDevices, err := resourceInstanceBlockDevicesV2(d, vL.([]interface{}))
if err != nil {
return err
}
createOpts = &bootfromvolume.CreateOptsExt{ createOpts = &bootfromvolume.CreateOptsExt{
CreateOptsBuilder: createOpts, CreateOptsBuilder: createOpts,
BlockDevice: blockDevices, BlockDevice: blockDevices,
@ -493,7 +499,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err
return CheckDeleted(d, err, "server") return CheckDeleted(d, err, "server")
} }
log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) log.Printf("[DEBUG] Retrieved Server %s: %+v", d.Id(), server)
d.Set("name", server.Name) d.Set("name", server.Name)
@ -613,24 +619,20 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e
log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove)
for _, g := range secgroupsToRemove.List() { for _, g := range secgroupsToRemove.List() {
err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() err := secgroups.RemoveServer(computeClient, d.Id(), g.(string)).ExtractErr()
if err != nil && err.Error() != "EOF" { if err != nil && err.Error() != "EOF" {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error removing security group (%s) from OpenStack server (%s): %s", g, d.Id(), err)
}
if errCode.Actual == 404 {
continue continue
} else {
return fmt.Errorf("Error removing security group (%s) from OpenStack server (%s): %s", g, d.Id(), err)
} }
return fmt.Errorf("Error removing security group (%s) from OpenStack server (%s): %s", g, d.Id(), err)
} else { } else {
log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g, d.Id()) log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g, d.Id())
} }
} }
for _, g := range secgroupsToAdd.List() { for _, g := range secgroupsToAdd.List() {
err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr() err := secgroups.AddServer(computeClient, d.Id(), g.(string)).ExtractErr()
if err != nil && err.Error() != "EOF" { if err != nil && err.Error() != "EOF" {
return fmt.Errorf("Error adding security group (%s) to OpenStack server (%s): %s", g, d.Id(), err) return fmt.Errorf("Error adding security group (%s) to OpenStack server (%s): %s", g, d.Id(), err)
} }
@ -873,11 +875,7 @@ func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID stri
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
s, err := servers.Get(client, instanceID).Extract() s, err := servers.Get(client, instanceID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return nil, "", err
}
if errCode.Actual == 404 {
return s, "DELETED", nil return s, "DELETED", nil
} }
return nil, "", err return nil, "", err
@ -970,17 +968,21 @@ func getInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.Res
allPages, err := tenantnetworks.List(computeClient).AllPages() allPages, err := tenantnetworks.List(computeClient).AllPages()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok { log.Printf("[DEBUG] os-tenant-networks disabled")
return nil, err tenantNetworkExt = false
} }
if errCode.Actual == 404 || errCode.Actual == 403 { log.Printf("[DEBUG] Err looks like: %+v", err)
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 403 {
log.Printf("[DEBUG] os-tenant-networks disabled.")
tenantNetworkExt = false tenantNetworkExt = false
} else { } else {
return nil, err return nil, err
} }
} }
}
networkID := "" networkID := ""
networkName := "" networkName := ""
@ -1137,13 +1139,12 @@ func associateFloatingIPsToInstance(computeClient *gophercloud.ServiceClient, d
} }
func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error {
associateOpts := floatingip.AssociateOpts{ associateOpts := floatingips.AssociateOpts{
ServerID: instanceID,
FloatingIP: floatingIP, FloatingIP: floatingIP,
FixedIP: fixedIP, FixedIP: fixedIP,
} }
if err := floatingip.AssociateInstance(computeClient, associateOpts).ExtractErr(); err != nil { if err := floatingips.AssociateInstance(computeClient, instanceID, associateOpts).ExtractErr(); err != nil {
return fmt.Errorf("Error associating floating IP: %s", err) return fmt.Errorf("Error associating floating IP: %s", err)
} }
@ -1151,13 +1152,11 @@ func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, flo
} }
func disassociateFloatingIPFromInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error { func disassociateFloatingIPFromInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error {
associateOpts := floatingip.AssociateOpts{ disassociateOpts := floatingips.DisassociateOpts{
ServerID: instanceID,
FloatingIP: floatingIP, FloatingIP: floatingIP,
FixedIP: fixedIP,
} }
if err := floatingip.DisassociateInstance(computeClient, associateOpts).ExtractErr(); err != nil { if err := floatingips.DisassociateInstance(computeClient, instanceID, disassociateOpts).ExtractErr(); err != nil {
return fmt.Errorf("Error disassociating floating IP: %s", err) return fmt.Errorf("Error disassociating floating IP: %s", err)
} }
@ -1172,24 +1171,45 @@ func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
return m return m
} }
func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) []bootfromvolume.BlockDevice { func resourceInstanceBlockDevicesV2(d *schema.ResourceData, bds []interface{}) ([]bootfromvolume.BlockDevice, error) {
blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds)) blockDeviceOpts := make([]bootfromvolume.BlockDevice, len(bds))
for i, bd := range bds { for i, bd := range bds {
bdM := bd.(map[string]interface{}) bdM := bd.(map[string]interface{})
sourceType := bootfromvolume.SourceType(bdM["source_type"].(string))
blockDeviceOpts[i] = bootfromvolume.BlockDevice{ blockDeviceOpts[i] = bootfromvolume.BlockDevice{
UUID: bdM["uuid"].(string), UUID: bdM["uuid"].(string),
SourceType: sourceType,
VolumeSize: bdM["volume_size"].(int), VolumeSize: bdM["volume_size"].(int),
DestinationType: bdM["destination_type"].(string),
BootIndex: bdM["boot_index"].(int), BootIndex: bdM["boot_index"].(int),
DeleteOnTermination: bdM["delete_on_termination"].(bool), DeleteOnTermination: bdM["delete_on_termination"].(bool),
GuestFormat: bdM["guest_format"].(string), GuestFormat: bdM["guest_format"].(string),
} }
sourceType := bdM["source_type"].(string)
switch sourceType {
case "blank":
blockDeviceOpts[i].SourceType = bootfromvolume.SourceBlank
case "image":
blockDeviceOpts[i].SourceType = bootfromvolume.SourceImage
case "snapshot":
blockDeviceOpts[i].SourceType = bootfromvolume.SourceSnapshot
case "volume":
blockDeviceOpts[i].SourceType = bootfromvolume.SourceVolume
default:
return blockDeviceOpts, fmt.Errorf("unknown block device source type %s", sourceType)
}
destinationType := bdM["destination_type"].(string)
switch destinationType {
case "local":
blockDeviceOpts[i].DestinationType = bootfromvolume.DestinationLocal
case "volume":
blockDeviceOpts[i].DestinationType = bootfromvolume.DestinationVolume
default:
return blockDeviceOpts, fmt.Errorf("unknown block device destination type %s", destinationType)
}
} }
log.Printf("[DEBUG] Block Device Options: %+v", blockDeviceOpts) log.Printf("[DEBUG] Block Device Options: %+v", blockDeviceOpts)
return blockDeviceOpts return blockDeviceOpts, nil
} }
func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints { func resourceInstanceSchedulerHintsV2(d *schema.ResourceData, schedulerHintsRaw map[string]interface{}) schedulerhints.SchedulerHints {
@ -1291,19 +1311,14 @@ func setImageInformation(computeClient *gophercloud.ServiceClient, server *serve
if imageId != "" { if imageId != "" {
d.Set("image_id", imageId) d.Set("image_id", imageId)
if image, err := images.Get(computeClient, imageId).Extract(); err != nil { if image, err := images.Get(computeClient, imageId).Extract(); err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return err
}
if errCode.Actual == 404 {
// If the image name can't be found, set the value to "Image not found". // 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 // 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. // but the instance still has a record from when it existed.
d.Set("image_name", "Image not found") d.Set("image_name", "Image not found")
return nil return nil
} else {
return err
} }
return err
} else { } else {
d.Set("image_name", image.Name) d.Set("image_name", image.Name)
} }

View File

@ -8,13 +8,13 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
func TestAccComputeV2Instance_basic(t *testing.T) { func TestAccComputeV2Instance_basic(t *testing.T) {
@ -431,7 +431,7 @@ func TestAccComputeV2Instance_volumeAttachToNewInstance(t *testing.T) {
func TestAccComputeV2Instance_floatingIPAttachGlobally(t *testing.T) { func TestAccComputeV2Instance_floatingIPAttachGlobally(t *testing.T) {
var instance servers.Server var instance servers.Server
var fip floatingip.FloatingIP var fip floatingips.FloatingIP
var testAccComputeV2Instance_floatingIPAttachGlobally = fmt.Sprintf(` var testAccComputeV2Instance_floatingIPAttachGlobally = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip" { resource "openstack_compute_floatingip_v2" "myip" {
} }
@ -466,7 +466,7 @@ func TestAccComputeV2Instance_floatingIPAttachGlobally(t *testing.T) {
func TestAccComputeV2Instance_floatingIPAttachToNetwork(t *testing.T) { func TestAccComputeV2Instance_floatingIPAttachToNetwork(t *testing.T) {
var instance servers.Server var instance servers.Server
var fip floatingip.FloatingIP var fip floatingips.FloatingIP
var testAccComputeV2Instance_floatingIPAttachToNetwork = fmt.Sprintf(` var testAccComputeV2Instance_floatingIPAttachToNetwork = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip" { resource "openstack_compute_floatingip_v2" "myip" {
} }
@ -502,7 +502,7 @@ func TestAccComputeV2Instance_floatingIPAttachToNetwork(t *testing.T) {
func TestAccComputeV2Instance_floatingIPAttachAndChange(t *testing.T) { func TestAccComputeV2Instance_floatingIPAttachAndChange(t *testing.T) {
var instance servers.Server var instance servers.Server
var fip floatingip.FloatingIP var fip floatingips.FloatingIP
var testAccComputeV2Instance_floatingIPAttachToNetwork_1 = fmt.Sprintf(` var testAccComputeV2Instance_floatingIPAttachToNetwork_1 = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip_1" { resource "openstack_compute_floatingip_v2" "myip_1" {
} }
@ -1098,11 +1098,7 @@ func testAccCheckComputeV2InstanceDoesNotExist(t *testing.T, n string, instance
_, err = servers.Get(computeClient, instance.ID).Extract() _, err = servers.Get(computeClient, instance.ID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return err
}
if errCode.Actual == 404 {
return nil return nil
} }
return err return err
@ -1124,7 +1120,7 @@ func testAccCheckComputeV2InstanceMetadata(
continue continue
} }
if v == value.(string) { if v == value {
return nil return nil
} }
@ -1221,7 +1217,7 @@ func testAccCheckComputeV2InstanceBootVolumeAttachment(
} }
func testAccCheckComputeV2InstanceFloatingIPAttach( func testAccCheckComputeV2InstanceFloatingIPAttach(
instance *servers.Server, fip *floatingip.FloatingIP) resource.TestCheckFunc { instance *servers.Server, fip *floatingips.FloatingIP) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
if fip.InstanceID == instance.ID { if fip.InstanceID == instance.ID {
return nil return nil

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
) )
func resourceComputeKeypairV2() *schema.Resource { func resourceComputeKeypairV2() *schema.Resource {

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
) )
func TestAccComputeV2Keypair_basic(t *testing.T) { func TestAccComputeV2Keypair_basic(t *testing.T) {

View File

@ -7,11 +7,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
) )
func resourceComputeSecGroupV2() *schema.Resource { func resourceComputeSecGroupV2() *schema.Resource {
@ -197,15 +197,11 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e
rule := resourceSecGroupRuleV2(d, r) rule := resourceSecGroupRuleV2(d, r)
err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr() err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err)
}
if errCode.Actual == 404 {
continue continue
} else {
return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id())
} }
return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id())
} else { } else {
log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err)
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
) )
func TestAccComputeV2SecGroup_basic(t *testing.T) { func TestAccComputeV2SecGroup_basic(t *testing.T) {

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups"
) )
func resourceComputeServerGroupV2() *schema.Resource { func resourceComputeServerGroupV2() *schema.Resource {

View File

@ -7,8 +7,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/servergroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
) )
func TestAccComputeV2ServerGroup_basic(t *testing.T) { func TestAccComputeV2ServerGroup_basic(t *testing.T) {

View File

@ -5,10 +5,10 @@ import (
"log" "log"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
) )
func resourceFWFirewallV1() *schema.Resource { func resourceFWFirewallV1() *schema.Resource {
@ -231,14 +231,11 @@ func waitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id str
log.Printf("[DEBUG] Get firewall %s => %#v", id, fw) log.Printf("[DEBUG] Get firewall %s => %#v", id, fw)
if err != nil { if err != nil {
httpStatus := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
log.Printf("[DEBUG] Get firewall %s status is %d", id, httpStatus.Actual)
if httpStatus.Actual == 404 {
log.Printf("[DEBUG] Firewall %s is actually deleted", id) log.Printf("[DEBUG] Firewall %s is actually deleted", id)
return "", "DELETED", nil return "", "DELETED", nil
} }
return nil, "", fmt.Errorf("Unexpected status code %d", httpStatus.Actual) return nil, "", fmt.Errorf("Unexpected error: %s", err)
} }
log.Printf("[DEBUG] Firewall %s deletion is pending", id) log.Printf("[DEBUG] Firewall %s deletion is pending", id)

View File

@ -5,10 +5,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
) )
func TestAccFWFirewallV1_basic(t *testing.T) { func TestAccFWFirewallV1_basic(t *testing.T) {
@ -47,13 +47,13 @@ func testAccCheckFWFirewallV1Destroy(s *terraform.State) error {
if rs.Type != "openstack_firewall" { if rs.Type != "openstack_firewall" {
continue continue
} }
_, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract() _, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract()
if err == nil { if err == nil {
return fmt.Errorf("Firewall (%s) still exists.", rs.Primary.ID) return fmt.Errorf("Firewall (%s) still exists.", rs.Primary.ID)
} }
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); !ok {
if !ok || httpError.Actual != 404 { return err
return httpError
} }
} }
return nil return nil
@ -84,19 +84,15 @@ func testAccCheckFWFirewallV1Exists(n, expectedName, expectedDescription string,
// if we get a 404 error. Fail on any other error. // if we get a 404 error. Fail on any other error.
found, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract() found, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok || httpError.Actual != 404 {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
return err
} }
break break
} }
if err != nil {
return err
}
if found.Name != expectedName { if found.Name != expectedName {
return fmt.Errorf("Expected Name to be <%s> but found <%s>", expectedName, found.Name) return fmt.Errorf("Expected Name to be <%s> but found <%s>", expectedName, found.Name)
} }

View File

@ -5,9 +5,10 @@ import (
"log" "log"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
) )
func resourceFWPolicyV1() *schema.Resource { func resourceFWPolicyV1() *schema.Resource {
@ -61,7 +62,6 @@ func resourceFWPolicyV1() *schema.Resource {
} }
func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error { func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) networkingClient, err := config.networkingV2Client(d.Get("region").(string))
if err != nil { if err != nil {
@ -130,7 +130,6 @@ func resourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error {
} }
func resourceFWPolicyV1Update(d *schema.ResourceData, meta interface{}) error { func resourceFWPolicyV1Update(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) networkingClient, err := config.networkingV2Client(d.Get("region").(string))
if err != nil { if err != nil {
@ -179,24 +178,38 @@ func resourceFWPolicyV1Delete(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error creating OpenStack networking client: %s", err) return fmt.Errorf("Error creating OpenStack networking client: %s", err)
} }
for i := 0; i < 15; i++ { stateConf := &resource.StateChangeConf{
Pending: []string{"ACTIVE"},
err = policies.Delete(networkingClient, d.Id()).Err Target: []string{"DELETED"},
if err == nil { Refresh: waitForFirewallPolicyDeletion(networkingClient, d.Id()),
break Timeout: 120 * time.Second,
Delay: 0,
MinTimeout: 2 * time.Second,
} }
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, err = stateConf.WaitForState(); err != nil {
if !ok || httpError.Actual != 409 {
return err return err
} }
return nil
}
func waitForFirewallPolicyDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
err := policies.Delete(networkingClient, id).Err
if err == nil {
return "", "DELETED", nil
}
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
// This error usually means that the policy is attached // This error usually means that the policy is attached
// to a firewall. At this point, the firewall is probably // to a firewall. At this point, the firewall is probably
// being delete. So, we retry a few times. // being delete. So, we retry a few times.
return nil, "ACTIVE", nil
time.Sleep(time.Second * 2) }
} }
return err return nil, "ACTIVE", err
}
} }

View File

@ -5,10 +5,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
) )
func TestAccFWPolicyV1_basic(t *testing.T) { func TestAccFWPolicyV1_basic(t *testing.T) {
@ -80,9 +80,8 @@ func testAccCheckFWPolicyV1Destroy(s *terraform.State) error {
if err == nil { if err == nil {
return fmt.Errorf("Firewall policy (%s) still exists.", rs.Primary.ID) return fmt.Errorf("Firewall policy (%s) still exists.", rs.Primary.ID)
} }
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); !ok {
if !ok || httpError.Actual != 404 { return err
return httpError
} }
} }
return nil return nil
@ -113,19 +112,15 @@ func testAccCheckFWPolicyV1Exists(n, name, description string, ruleCount int) re
// if we get a 404 error. Fail on any other error. // if we get a 404 error. Fail on any other error.
found, err = policies.Get(networkingClient, rs.Primary.ID).Extract() found, err = policies.Get(networkingClient, rs.Primary.ID).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok || httpError.Actual != 404 {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
return err
} }
break break
} }
if err != nil {
return err
}
if name != found.Name { if name != found.Name {
return fmt.Errorf("Expected name <%s>, but found <%s>", name, found.Name) return fmt.Errorf("Expected name <%s>, but found <%s>", name, found.Name)
} }

View File

@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
) )
func resourceFWRuleV1() *schema.Resource { func resourceFWRuleV1() *schema.Resource {
@ -86,13 +87,14 @@ func resourceFWRuleV1Create(d *schema.ResourceData, meta interface{}) error {
} }
enabled := d.Get("enabled").(bool) enabled := d.Get("enabled").(bool)
ipVersion := resourceFWRuleV1DetermineIPVersion(d.Get("ip_version").(int))
ruleConfiguration := rules.CreateOpts{ ruleConfiguration := rules.CreateOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
Description: d.Get("description").(string), Description: d.Get("description").(string),
Protocol: d.Get("protocol").(string), Protocol: d.Get("protocol").(string),
Action: d.Get("action").(string), Action: d.Get("action").(string),
IPVersion: d.Get("ip_version").(int), IPVersion: ipVersion,
SourceIPAddress: d.Get("source_ip_address").(string), SourceIPAddress: d.Get("source_ip_address").(string),
DestinationIPAddress: d.Get("destination_ip_address").(string), DestinationIPAddress: d.Get("destination_ip_address").(string),
SourcePort: d.Get("source_port").(string), SourcePort: d.Get("source_port").(string),
@ -101,6 +103,11 @@ func resourceFWRuleV1Create(d *schema.ResourceData, meta interface{}) error {
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
} }
if v, ok := d.GetOk("ip_version"); ok {
ipVersion := resourceFWRuleV1DetermineIPVersion(v.(int))
ruleConfiguration.IPVersion = ipVersion
}
log.Printf("[DEBUG] Create firewall rule: %#v", ruleConfiguration) log.Printf("[DEBUG] Create firewall rule: %#v", ruleConfiguration)
rule, err := rules.Create(networkingClient, ruleConfiguration).Extract() rule, err := rules.Create(networkingClient, ruleConfiguration).Extract()
@ -156,39 +163,54 @@ func resourceFWRuleV1Update(d *schema.ResourceData, meta interface{}) error {
opts := rules.UpdateOpts{} opts := rules.UpdateOpts{}
if d.HasChange("name") { if d.HasChange("name") {
opts.Name = d.Get("name").(string) v := d.Get("name").(string)
opts.Name = &v
} }
if d.HasChange("description") { if d.HasChange("description") {
opts.Description = d.Get("description").(string) v := d.Get("description").(string)
opts.Description = &v
} }
if d.HasChange("protocol") { if d.HasChange("protocol") {
opts.Protocol = d.Get("protocol").(string) v := d.Get("protocol").(string)
opts.Protocol = &v
} }
if d.HasChange("action") { if d.HasChange("action") {
opts.Action = d.Get("action").(string) v := d.Get("action").(string)
opts.Action = &v
} }
if d.HasChange("ip_version") { if d.HasChange("ip_version") {
opts.IPVersion = d.Get("ip_version").(int) v := d.Get("ip_version").(int)
ipVersion := resourceFWRuleV1DetermineIPVersion(v)
opts.IPVersion = &ipVersion
} }
if d.HasChange("source_ip_address") { if d.HasChange("source_ip_address") {
sourceIPAddress := d.Get("source_ip_address").(string) v := d.Get("source_ip_address").(string)
opts.SourceIPAddress = &sourceIPAddress opts.SourceIPAddress = &v
} }
if d.HasChange("destination_ip_address") { if d.HasChange("destination_ip_address") {
destinationIPAddress := d.Get("destination_ip_address").(string) v := d.Get("destination_ip_address").(string)
opts.DestinationIPAddress = &destinationIPAddress opts.DestinationIPAddress = &v
} }
if d.HasChange("source_port") { if d.HasChange("source_port") {
sourcePort := d.Get("source_port").(string) v := d.Get("source_port").(string)
opts.SourcePort = &sourcePort opts.SourcePort = &v
} }
if d.HasChange("destination_port") { if d.HasChange("destination_port") {
destinationPort := d.Get("destination_port").(string) v := d.Get("destination_port").(string)
opts.DestinationPort = &destinationPort opts.DestinationPort = &v
} }
if d.HasChange("enabled") { if d.HasChange("enabled") {
enabled := d.Get("enabled").(bool) v := d.Get("enabled").(bool)
opts.Enabled = &enabled opts.Enabled = &v
} }
log.Printf("[DEBUG] Updating firewall rules: %#v", opts) log.Printf("[DEBUG] Updating firewall rules: %#v", opts)
@ -216,7 +238,7 @@ func resourceFWRuleV1Delete(d *schema.ResourceData, meta interface{}) error {
} }
if rule.PolicyID != "" { if rule.PolicyID != "" {
err := policies.RemoveRule(networkingClient, rule.PolicyID, rule.ID) _, err := policies.RemoveRule(networkingClient, rule.PolicyID, rule.ID).Extract()
if err != nil { if err != nil {
return err return err
} }
@ -224,3 +246,16 @@ func resourceFWRuleV1Delete(d *schema.ResourceData, meta interface{}) error {
return rules.Delete(networkingClient, d.Id()).Err return rules.Delete(networkingClient, d.Id()).Err
} }
func resourceFWRuleV1DetermineIPVersion(ipv int) gophercloud.IPVersion {
// Determine the IP Version
var ipVersion gophercloud.IPVersion
switch ipv {
case 4:
ipVersion = gophercloud.IPv4
case 6:
ipVersion = gophercloud.IPv6
}
return ipVersion
}

View File

@ -6,10 +6,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
) )
func TestAccFWRuleV1_basic(t *testing.T) { func TestAccFWRuleV1_basic(t *testing.T) {
@ -88,9 +88,8 @@ func testAccCheckFWRuleV1Destroy(s *terraform.State) error {
if err == nil { if err == nil {
return fmt.Errorf("Firewall rule (%s) still exists.", rs.Primary.ID) return fmt.Errorf("Firewall rule (%s) still exists.", rs.Primary.ID)
} }
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); !ok {
if !ok || httpError.Actual != 404 { return err
return httpError
} }
} }
return nil return nil
@ -121,19 +120,15 @@ func testAccCheckFWRuleV1Exists(n string, expected *rules.Rule) resource.TestChe
// if we get a 404 error. Fail on any other error. // if we get a 404 error. Fail on any other error.
found, err = rules.Get(networkingClient, rs.Primary.ID).Extract() found, err = rules.Get(networkingClient, rs.Primary.ID).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok || httpError.Actual != 404 {
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
return err
} }
break break
} }
if err != nil {
return err
}
expected.ID = found.ID expected.ID = found.ID
// Erase the tenant id because we don't want to compare // Erase the tenant id because we don't want to compare
// it as long it is not present in the expected // it as long it is not present in the expected

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
) )
func resourceListenerV2() *schema.Resource { func resourceListenerV2() *schema.Resource {
@ -178,7 +178,7 @@ func resourceListenerV2Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LBV2 listener") return CheckDeleted(d, err, "LBV2 listener")
} }
log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 listener %s: %+v", d.Id(), listener) log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 listener %s: %+v", d.Id(), listener)
d.Set("id", listener.ID) d.Set("id", listener.ID)
d.Set("name", listener.Name) d.Set("name", listener.Name)
@ -285,27 +285,29 @@ func waitForListenerDelete(networkingClient *gophercloud.ServiceClient, listener
listener, err := listeners.Get(networkingClient, listenerID).Extract() listener, err := listeners.Get(networkingClient, listenerID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return listener, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID)
return listener, "DELETED", nil return listener, "DELETED", nil
} }
return listener, "ACTIVE", err
} }
log.Printf("[DEBUG] Openstack LBaaSV2 listener: %+v", listener) log.Printf("[DEBUG] Openstack LBaaSV2 listener: %+v", listener)
err = listeners.Delete(networkingClient, listenerID).ExtractErr() err = listeners.Delete(networkingClient, listenerID).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return listener, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 listener %s", listenerID)
return listener, "DELETED", nil return listener, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LBaaSV2 listener (%s) is still in use.", listenerID)
return listener, "ACTIVE", nil
}
}
return listener, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LBaaSV2 listener %s still active.", listenerID) log.Printf("[DEBUG] OpenStack LBaaSV2 listener %s still active.", listenerID)

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
) )
func TestAccLBV2Listener_basic(t *testing.T) { func TestAccLBV2Listener_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
) )
func resourceLoadBalancerV2() *schema.Resource { func resourceLoadBalancerV2() *schema.Resource {
@ -138,7 +138,7 @@ func resourceLoadBalancerV2Read(d *schema.ResourceData, meta interface{}) error
return CheckDeleted(d, err, "LoadBalancerV2") return CheckDeleted(d, err, "LoadBalancerV2")
} }
log.Printf("[DEBUG] Retreived OpenStack LoadBalancerV2 %s: %+v", d.Id(), lb) log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 LoadBalancer %s: %+v", d.Id(), lb)
d.Set("name", lb.Name) d.Set("name", lb.Name)
d.Set("description", lb.Description) d.Set("description", lb.Description)
@ -199,7 +199,7 @@ func resourceLoadBalancerV2Delete(d *schema.ResourceData, meta interface{}) erro
_, err = stateConf.WaitForState() _, err = stateConf.WaitForState()
if err != nil { if err != nil {
return fmt.Errorf("Error deleting OpenStack LB Pool: %s", err) return fmt.Errorf("Error deleting OpenStack LBaaSV2 LoadBalancer: %s", err)
} }
d.SetId("") d.SetId("")
@ -213,7 +213,7 @@ func waitForLoadBalancerActive(networkingClient *gophercloud.ServiceClient, lbID
return nil, "", err return nil, "", err
} }
log.Printf("[DEBUG] OpenStack LoadBalancer: %+v", lb) log.Printf("[DEBUG] OpenStack LBaaSV2 LoadBalancer: %+v", lb)
if lb.ProvisioningStatus == "ACTIVE" { if lb.ProvisioningStatus == "ACTIVE" {
return lb, "ACTIVE", nil return lb, "ACTIVE", nil
} }
@ -224,34 +224,36 @@ func waitForLoadBalancerActive(networkingClient *gophercloud.ServiceClient, lbID
func waitForLoadBalancerDelete(networkingClient *gophercloud.ServiceClient, lbID string) resource.StateRefreshFunc { func waitForLoadBalancerDelete(networkingClient *gophercloud.ServiceClient, lbID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
log.Printf("[DEBUG] Attempting to delete OpenStack LoadBalancerV2 %s", lbID) log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 LoadBalancer %s", lbID)
lb, err := loadbalancers.Get(networkingClient, lbID).Extract() lb, err := loadbalancers.Get(networkingClient, lbID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok { log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 LoadBalancer %s", lbID)
return lb, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LoadBalancerV2 %s", lbID)
return lb, "DELETED", nil return lb, "DELETED", nil
} }
return lb, "ACTIVE", err
} }
log.Printf("[DEBUG] Openstack LoadBalancerV2: %+v", lb) log.Printf("[DEBUG] Openstack LoadBalancerV2: %+v", lb)
err = loadbalancers.Delete(networkingClient, lbID).ExtractErr() err = loadbalancers.Delete(networkingClient, lbID).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok { log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 LoadBalancer %s", lbID)
return lb, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LoadBalancerV2 %s", lbID)
return lb, "DELETED", nil return lb, "DELETED", nil
} }
}
log.Printf("[DEBUG] OpenStack LoadBalancerV2 %s still active.", lbID) if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LBaaSV2 LoadBalancer (%s) is still in use.", lbID)
return lb, "ACTIVE", nil
}
}
return lb, "ACTIVE", err
}
log.Printf("[DEBUG] OpenStack LBaaSV2 LoadBalancer (%s) still active.", lbID)
return lb, "ACTIVE", nil return lb, "ACTIVE", nil
} }
} }

View File

@ -2,12 +2,11 @@ package openstack
import ( import (
"fmt" "fmt"
"log"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
) )
func TestAccLBV2LoadBalancer_basic(t *testing.T) { func TestAccLBV2LoadBalancer_basic(t *testing.T) {
@ -42,8 +41,6 @@ func testAccCheckLBV2LoadBalancerDestroy(s *terraform.State) error {
} }
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
log.Printf("[FINDME] rs TYPE is: %#v", rs.Type)
if rs.Type != "openstack_lb_loadbalancer_v2" { if rs.Type != "openstack_lb_loadbalancer_v2" {
continue continue
} }

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
) )
func resourceLBMemberV1() *schema.Resource { func resourceLBMemberV1() *schema.Resource {
@ -104,8 +104,9 @@ func resourceLBMemberV1Create(d *schema.ResourceData, meta interface{}) error {
d.SetId(m.ID) d.SetId(m.ID)
// Due to the way Gophercloud is currently set up, AdminStateUp must be set post-create // Due to the way Gophercloud is currently set up, AdminStateUp must be set post-create
asu := d.Get("admin_state_up").(bool)
updateOpts := members.UpdateOpts{ updateOpts := members.UpdateOpts{
AdminStateUp: d.Get("admin_state_up").(bool), AdminStateUp: &asu,
} }
log.Printf("[DEBUG] OpenStack LB Member Update Options: %#v", createOpts) log.Printf("[DEBUG] OpenStack LB Member Update Options: %#v", createOpts)
@ -129,7 +130,7 @@ func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LB member") return CheckDeleted(d, err, "LB member")
} }
log.Printf("[DEBUG] Retreived OpenStack LB member %s: %+v", d.Id(), m) log.Printf("[DEBUG] Retrieved OpenStack LB member %s: %+v", d.Id(), m)
d.Set("address", m.Address) d.Set("address", m.Address)
d.Set("pool_id", m.PoolID) d.Set("pool_id", m.PoolID)
@ -150,7 +151,7 @@ func resourceLBMemberV1Update(d *schema.ResourceData, meta interface{}) error {
var updateOpts members.UpdateOpts var updateOpts members.UpdateOpts
if d.HasChange("admin_state_up") { if d.HasChange("admin_state_up") {
asu := d.Get("admin_state_up").(bool) asu := d.Get("admin_state_up").(bool)
updateOpts.AdminStateUp = asu updateOpts.AdminStateUp = &asu
} }
log.Printf("[DEBUG] Updating LB member %s with options: %+v", d.Id(), updateOpts) log.Printf("[DEBUG] Updating LB member %s with options: %+v", d.Id(), updateOpts)
@ -215,14 +216,11 @@ func waitForLBMemberDelete(networkingClient *gophercloud.ServiceClient, memberId
m, err := members.Get(networkingClient, memberId).Extract() m, err := members.Get(networkingClient, memberId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return m, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB member %s", memberId) log.Printf("[DEBUG] Successfully deleted OpenStack LB member %s", memberId)
return m, "DELETED", nil return m, "DELETED", nil
} }
return m, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LB member %s still active.", memberId) log.Printf("[DEBUG] OpenStack LB member %s still active.", memberId)

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members"
) )
func TestAccLBV1Member_basic(t *testing.T) { func TestAccLBV1Member_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
) )
func resourceMemberV2() *schema.Resource { func resourceMemberV2() *schema.Resource {
@ -99,9 +99,8 @@ func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error creating OpenStack networking client: %s", err) return fmt.Errorf("Error creating OpenStack networking client: %s", err)
} }
subnetID := d.Get("subnet_id").(string)
adminStateUp := d.Get("admin_state_up").(bool) adminStateUp := d.Get("admin_state_up").(bool)
createOpts := pools.MemberCreateOpts{ createOpts := pools.CreateMemberOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
Address: d.Get("address").(string), Address: d.Get("address").(string),
@ -109,15 +108,16 @@ func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error {
Weight: d.Get("weight").(int), Weight: d.Get("weight").(int),
AdminStateUp: &adminStateUp, AdminStateUp: &adminStateUp,
} }
// Must omit if not set // Must omit if not set
if subnetID != "" { if v, ok := d.GetOk("subnet_id"); ok {
createOpts.SubnetID = subnetID createOpts.SubnetID = v.(string)
} }
poolID := d.Get("pool_id").(string) poolID := d.Get("pool_id").(string)
log.Printf("[DEBUG] Create Options: %#v", createOpts) log.Printf("[DEBUG] Create Options: %#v", createOpts)
member, err := pools.CreateAssociateMember(networkingClient, poolID, createOpts).ExtractMember() member, err := pools.CreateMember(networkingClient, poolID, createOpts).Extract()
if err != nil { if err != nil {
return fmt.Errorf("Error creating OpenStack LBaaSV2 member: %s", err) return fmt.Errorf("Error creating OpenStack LBaaSV2 member: %s", err)
} }
@ -151,12 +151,12 @@ func resourceMemberV2Read(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error creating OpenStack networking client: %s", err) return fmt.Errorf("Error creating OpenStack networking client: %s", err)
} }
member, err := pools.GetAssociateMember(networkingClient, d.Get("pool_id").(string), d.Id()).ExtractMember() member, err := pools.GetMember(networkingClient, d.Get("pool_id").(string), d.Id()).Extract()
if err != nil { if err != nil {
return CheckDeleted(d, err, "LBV2 Member") return CheckDeleted(d, err, "LBV2 Member")
} }
log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Member %s: %+v", d.Id(), member) log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Member %s: %+v", d.Id(), member)
d.Set("name", member.Name) d.Set("name", member.Name)
d.Set("weight", member.Weight) d.Set("weight", member.Weight)
@ -177,7 +177,7 @@ func resourceMemberV2Update(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error creating OpenStack networking client: %s", err) return fmt.Errorf("Error creating OpenStack networking client: %s", err)
} }
var updateOpts pools.MemberUpdateOpts var updateOpts pools.UpdateMemberOpts
if d.HasChange("name") { if d.HasChange("name") {
updateOpts.Name = d.Get("name").(string) updateOpts.Name = d.Get("name").(string)
} }
@ -191,7 +191,7 @@ func resourceMemberV2Update(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Member %s with options: %+v", d.Id(), updateOpts) log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Member %s with options: %+v", d.Id(), updateOpts)
_, err = pools.UpdateAssociateMember(networkingClient, d.Get("pool_id").(string), d.Id(), updateOpts).ExtractMember() _, err = pools.UpdateMember(networkingClient, d.Get("pool_id").(string), d.Id(), updateOpts).Extract()
if err != nil { if err != nil {
return fmt.Errorf("Error updating OpenStack LBaaSV2 Member: %s", err) return fmt.Errorf("Error updating OpenStack LBaaSV2 Member: %s", err)
} }
@ -226,7 +226,7 @@ func resourceMemberV2Delete(d *schema.ResourceData, meta interface{}) error {
func waitForMemberActive(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc { func waitForMemberActive(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
member, err := pools.GetAssociateMember(networkingClient, poolID, memberID).ExtractMember() member, err := pools.GetMember(networkingClient, poolID, memberID).Extract()
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -241,29 +241,31 @@ func waitForMemberDelete(networkingClient *gophercloud.ServiceClient, poolID str
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Member %s", memberID) log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Member %s", memberID)
member, err := pools.GetAssociateMember(networkingClient, poolID, memberID).ExtractMember() member, err := pools.GetMember(networkingClient, poolID, memberID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return member, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID)
return member, "DELETED", nil return member, "DELETED", nil
} }
return member, "ACTIVE", err
} }
log.Printf("[DEBUG] Openstack LBaaSV2 Member: %+v", member) log.Printf("[DEBUG] Openstack LBaaSV2 Member: %+v", member)
err = pools.DeleteMember(networkingClient, poolID, memberID).ExtractErr() err = pools.DeleteMember(networkingClient, poolID, memberID).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return member, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID)
return member, "DELETED", nil return member, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LBaaSV2 Member (%s) is still in use.", memberID)
return member, "ACTIVE", nil
}
}
return member, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LBaaSV2 Member %s still active.", memberID) log.Printf("[DEBUG] OpenStack LBaaSV2 Member %s still active.", memberID)

View File

@ -2,12 +2,11 @@ package openstack
import ( import (
"fmt" "fmt"
"log"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
) )
func TestAccLBV2Member_basic(t *testing.T) { func TestAccLBV2Member_basic(t *testing.T) {
@ -42,14 +41,11 @@ func testAccCheckLBV2MemberDestroy(s *terraform.State) error {
} }
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
log.Printf("[FINDME] rs TYPE is: %T", rs)
if rs.Type != "openstack_lb_member_v2" { if rs.Type != "openstack_lb_member_v2" {
continue continue
} }
log.Printf("[FINDME] rs.Primary.Attributes: %#v", rs.Primary.Attributes) _, err := pools.GetMember(networkingClient, rs.Primary.Attributes["pool_id"], rs.Primary.ID).Extract()
_, err := pools.GetAssociateMember(networkingClient, rs.Primary.Attributes["pool_id"], rs.Primary.ID).ExtractMember()
if err == nil { if err == nil {
return fmt.Errorf("Member still exists: %s", rs.Primary.ID) return fmt.Errorf("Member still exists: %s", rs.Primary.ID)
} }
@ -75,7 +71,7 @@ func testAccCheckLBV2MemberExists(t *testing.T, n string, member *pools.Member)
return fmt.Errorf("(testAccCheckLBV2MemberExists) Error creating OpenStack networking client: %s", err) return fmt.Errorf("(testAccCheckLBV2MemberExists) Error creating OpenStack networking client: %s", err)
} }
found, err := pools.GetAssociateMember(networkingClient, rs.Primary.Attributes["pool_id"], rs.Primary.ID).ExtractMember() found, err := pools.GetMember(networkingClient, rs.Primary.Attributes["pool_id"], rs.Primary.ID).Extract()
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,8 +9,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
) )
func resourceLBMonitorV1() *schema.Resource { func resourceLBMonitorV1() *schema.Resource {
@ -90,7 +90,6 @@ func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error {
createOpts := monitors.CreateOpts{ createOpts := monitors.CreateOpts{
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
Type: d.Get("type").(string),
Delay: d.Get("delay").(int), Delay: d.Get("delay").(int),
Timeout: d.Get("timeout").(int), Timeout: d.Get("timeout").(int),
MaxRetries: d.Get("max_retries").(int), MaxRetries: d.Get("max_retries").(int),
@ -99,6 +98,11 @@ func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error {
HTTPMethod: d.Get("http_method").(string), HTTPMethod: d.Get("http_method").(string),
} }
if v, ok := d.GetOk("type"); ok {
monitorType := resourceLBMonitorV1DetermineType(v.(string))
createOpts.Type = monitorType
}
asuRaw := d.Get("admin_state_up").(string) asuRaw := d.Get("admin_state_up").(string)
if asuRaw != "" { if asuRaw != "" {
asu, err := strconv.ParseBool(asuRaw) asu, err := strconv.ParseBool(asuRaw)
@ -148,7 +152,7 @@ func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LB monitor") return CheckDeleted(d, err, "LB monitor")
} }
log.Printf("[DEBUG] Retreived OpenStack LB Monitor %s: %+v", d.Id(), m) log.Printf("[DEBUG] Retrieved OpenStack LB Monitor %s: %+v", d.Id(), m)
d.Set("type", m.Type) d.Set("type", m.Type)
d.Set("delay", m.Delay) d.Set("delay", m.Delay)
@ -225,6 +229,22 @@ func resourceLBMonitorV1Delete(d *schema.ResourceData, meta interface{}) error {
return nil return nil
} }
func resourceLBMonitorV1DetermineType(t string) monitors.MonitorType {
var monitorType monitors.MonitorType
switch t {
case "PING":
monitorType = monitors.TypePING
case "TCP":
monitorType = monitors.TypeTCP
case "HTTP":
monitorType = monitors.TypeHTTP
case "HTTPS":
monitorType = monitors.TypeHTTPS
}
return monitorType
}
func waitForLBMonitorActive(networkingClient *gophercloud.ServiceClient, monitorId string) resource.StateRefreshFunc { func waitForLBMonitorActive(networkingClient *gophercloud.ServiceClient, monitorId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
m, err := monitors.Get(networkingClient, monitorId).Extract() m, err := monitors.Get(networkingClient, monitorId).Extract()
@ -244,37 +264,39 @@ func waitForLBMonitorDelete(networkingClient *gophercloud.ServiceClient, monitor
m, err := monitors.Get(networkingClient, monitorId).Extract() m, err := monitors.Get(networkingClient, monitorId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return m, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB Monitor %s", monitorId) log.Printf("[DEBUG] Successfully deleted OpenStack LB Monitor %s", monitorId)
return m, "DELETED", nil return m, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 { if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LB Monitor (%s) is waiting for Pool to delete.", monitorId) log.Printf("[DEBUG] OpenStack LB Monitor (%s) is waiting for Pool to delete.", monitorId)
return m, "PENDING", nil return m, "PENDING", nil
} }
} }
return m, "ACTIVE", err
}
log.Printf("[DEBUG] OpenStack LB Monitor: %+v", m) log.Printf("[DEBUG] OpenStack LB Monitor: %+v", m)
err = monitors.Delete(networkingClient, monitorId).ExtractErr() err = monitors.Delete(networkingClient, monitorId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return m, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB Monitor %s", monitorId) log.Printf("[DEBUG] Successfully deleted OpenStack LB Monitor %s", monitorId)
return m, "DELETED", nil return m, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 { if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LB Monitor (%s) is waiting for Pool to delete.", monitorId) log.Printf("[DEBUG] OpenStack LB Monitor (%s) is waiting for Pool to delete.", monitorId)
return m, "PENDING", nil return m, "PENDING", nil
} }
} }
return m, "ACTIVE", err
}
log.Printf("[DEBUG] OpenStack LB Monitor %s still active.", monitorId) log.Printf("[DEBUG] OpenStack LB Monitor %s still active.", monitorId)
return m, "ACTIVE", nil return m, "ACTIVE", nil
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
) )
func TestAccLBV1Monitor_basic(t *testing.T) { func TestAccLBV1Monitor_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
) )
func resourceMonitorV2() *schema.Resource { func resourceMonitorV2() *schema.Resource {
@ -154,7 +154,7 @@ func resourceMonitorV2Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LBV2 Monitor") return CheckDeleted(d, err, "LBV2 Monitor")
} }
log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Monitor %s: %+v", d.Id(), monitor) log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Monitor %s: %+v", d.Id(), monitor)
d.Set("id", monitor.ID) d.Set("id", monitor.ID)
d.Set("tenant_id", monitor.TenantID) d.Set("tenant_id", monitor.TenantID)
@ -258,27 +258,29 @@ func waitForMonitorDelete(networkingClient *gophercloud.ServiceClient, monitorID
monitor, err := monitors.Get(networkingClient, monitorID).Extract() monitor, err := monitors.Get(networkingClient, monitorID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return monitor, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID)
return monitor, "DELETED", nil return monitor, "DELETED", nil
} }
return monitor, "ACTIVE", err
} }
log.Printf("[DEBUG] Openstack LBaaSV2 Monitor: %+v", monitor) log.Printf("[DEBUG] Openstack LBaaSV2 Monitor: %+v", monitor)
err = monitors.Delete(networkingClient, monitorID).ExtractErr() err = monitors.Delete(networkingClient, monitorID).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return monitor, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Monitor %s", monitorID)
return monitor, "DELETED", nil return monitor, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor (%s) is still in use.", monitorID)
return monitor, "ACTIVE", nil
}
}
return monitor, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor %s still active.", monitorID) log.Printf("[DEBUG] OpenStack LBaaSV2 Monitor %s still active.", monitorID)

View File

@ -2,12 +2,11 @@ package openstack
import ( import (
"fmt" "fmt"
"log"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
) )
func TestAccLBV2Monitor_basic(t *testing.T) { func TestAccLBV2Monitor_basic(t *testing.T) {
@ -44,13 +43,10 @@ func testAccCheckLBV2MonitorDestroy(s *terraform.State) error {
} }
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
log.Printf("[FINDME] rs TYPE is: %T", rs)
if rs.Type != "openstack_lb_monitor_v2" { if rs.Type != "openstack_lb_monitor_v2" {
continue continue
} }
log.Printf("[FINDME] rs.Primary.Attributes: %#v", rs.Primary.Attributes)
_, err := monitors.Get(networkingClient, rs.Primary.ID).Extract() _, err := monitors.Get(networkingClient, rs.Primary.ID).Extract()
if err == nil { if err == nil {
return fmt.Errorf("Monitor still exists: %s", rs.Primary.ID) return fmt.Errorf("Monitor still exists: %s", rs.Primary.ID)

View File

@ -10,10 +10,10 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/members"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
func resourceLBPoolV1() *schema.Resource { func resourceLBPoolV1() *schema.Resource {
@ -122,13 +122,21 @@ func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error {
createOpts := pools.CreateOpts{ createOpts := pools.CreateOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
Protocol: d.Get("protocol").(string),
SubnetID: d.Get("subnet_id").(string), SubnetID: d.Get("subnet_id").(string),
LBMethod: d.Get("lb_method").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
Provider: d.Get("lb_provider").(string), Provider: d.Get("lb_provider").(string),
} }
if v, ok := d.GetOk("protocol"); ok {
protocol := resourceLBPoolV1DetermineProtocol(v.(string))
createOpts.Protocol = protocol
}
if v, ok := d.GetOk("lb_method"); ok {
lbMethod := resourceLBPoolV1DetermineLBMethod(v.(string))
createOpts.LBMethod = lbMethod
}
log.Printf("[DEBUG] Create Options: %#v", createOpts) log.Printf("[DEBUG] Create Options: %#v", createOpts)
p, err := pools.Create(networkingClient, createOpts).Extract() p, err := pools.Create(networkingClient, createOpts).Extract()
if err != nil { if err != nil {
@ -187,7 +195,7 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LB pool") return CheckDeleted(d, err, "LB pool")
} }
log.Printf("[DEBUG] Retreived OpenStack LB Pool %s: %+v", d.Id(), p) log.Printf("[DEBUG] Retrieved OpenStack LB Pool %s: %+v", d.Id(), p)
d.Set("name", p.Name) d.Set("name", p.Name)
d.Set("protocol", p.Protocol) d.Set("protocol", p.Protocol)
@ -213,7 +221,9 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error {
// Gophercloud complains if one is empty. // Gophercloud complains if one is empty.
if d.HasChange("name") || d.HasChange("lb_method") { if d.HasChange("name") || d.HasChange("lb_method") {
updateOpts.Name = d.Get("name").(string) updateOpts.Name = d.Get("name").(string)
updateOpts.LBMethod = d.Get("lb_method").(string)
lbMethod := resourceLBPoolV1DetermineLBMethod(d.Get("lb_method").(string))
updateOpts.LBMethod = lbMethod
} }
log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts) log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts)
@ -379,6 +389,32 @@ func resourceLBMemberV1Hash(v interface{}) int {
return hashcode.String(buf.String()) return hashcode.String(buf.String())
} }
func resourceLBPoolV1DetermineProtocol(v string) pools.LBProtocol {
var protocol pools.LBProtocol
switch v {
case "TCP":
protocol = pools.ProtocolTCP
case "HTTP":
protocol = pools.ProtocolHTTP
case "HTTPS":
protocol = pools.ProtocolHTTPS
}
return protocol
}
func resourceLBPoolV1DetermineLBMethod(v string) pools.LBMethod {
var lbMethod pools.LBMethod
switch v {
case "ROUND_ROBIN":
lbMethod = pools.LBMethodRoundRobin
case "LEAST_CONNECTIONS":
lbMethod = pools.LBMethodLeastConnections
}
return lbMethod
}
func waitForLBPoolActive(networkingClient *gophercloud.ServiceClient, poolId string) resource.StateRefreshFunc { func waitForLBPoolActive(networkingClient *gophercloud.ServiceClient, poolId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
p, err := pools.Get(networkingClient, poolId).Extract() p, err := pools.Get(networkingClient, poolId).Extract()
@ -401,27 +437,21 @@ func waitForLBPoolDelete(networkingClient *gophercloud.ServiceClient, poolId str
p, err := pools.Get(networkingClient, poolId).Extract() p, err := pools.Get(networkingClient, poolId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB Pool %s", poolId) log.Printf("[DEBUG] Successfully deleted OpenStack LB Pool %s", poolId)
return p, "DELETED", nil return p, "DELETED", nil
} }
return p, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LB Pool: %+v", p) log.Printf("[DEBUG] OpenStack LB Pool: %+v", p)
err = pools.Delete(networkingClient, poolId).ExtractErr() err = pools.Delete(networkingClient, poolId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB Pool %s", poolId) log.Printf("[DEBUG] Successfully deleted OpenStack LB Pool %s", poolId)
return p, "DELETED", nil return p, "DELETED", nil
} }
return p, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LB Pool %s still active.", poolId) log.Printf("[DEBUG] OpenStack LB Pool %s still active.", poolId)

View File

@ -7,13 +7,13 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func TestAccLBV1Pool_basic(t *testing.T) { func TestAccLBV1Pool_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
) )
func resourcePoolV2() *schema.Resource { func resourcePoolV2() *schema.Resource {
@ -201,7 +201,7 @@ func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LBV2 Pool") return CheckDeleted(d, err, "LBV2 Pool")
} }
log.Printf("[DEBUG] Retreived OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool) log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool)
d.Set("lb_method", pool.LBMethod) d.Set("lb_method", pool.LBMethod)
d.Set("protocol", pool.Protocol) d.Set("protocol", pool.Protocol)
@ -291,27 +291,29 @@ func waitForPoolDelete(networkingClient *gophercloud.ServiceClient, poolID strin
pool, err := pools.Get(networkingClient, poolID).Extract() pool, err := pools.Get(networkingClient, poolID).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return pool, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID)
return pool, "DELETED", nil return pool, "DELETED", nil
} }
return pool, "ACTIVE", err
} }
log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool) log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool)
err = pools.Delete(networkingClient, poolID).ExtractErr() err = pools.Delete(networkingClient, poolID).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return pool, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID) log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID)
return pool, "DELETED", nil return pool, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LBaaSV2 Pool (%s) is still in use.", poolID)
return pool, "ACTIVE", nil
}
}
return pool, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID) log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID)

View File

@ -2,12 +2,11 @@ package openstack
import ( import (
"fmt" "fmt"
"log"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
) )
func TestAccLBV2Pool_basic(t *testing.T) { func TestAccLBV2Pool_basic(t *testing.T) {
@ -42,8 +41,6 @@ func testAccCheckLBV2PoolDestroy(s *terraform.State) error {
} }
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
log.Printf("[FINDME] rs TYPE is: %T", rs)
if rs.Type != "openstack_lb_pool_v2" { if rs.Type != "openstack_lb_pool_v2" {
continue continue
} }

View File

@ -5,11 +5,11 @@ import (
"log" "log"
"time" "time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
) )
func resourceLBVipV1() *schema.Resource { func resourceLBVipV1() *schema.Resource {
@ -171,7 +171,7 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error {
return CheckDeleted(d, err, "LB VIP") return CheckDeleted(d, err, "LB VIP")
} }
log.Printf("[DEBUG] Retreived OpenStack LB VIP %s: %+v", d.Id(), p) log.Printf("[DEBUG] Retrieved OpenStack LB VIP %s: %+v", d.Id(), p)
d.Set("name", p.Name) d.Set("name", p.Name)
d.Set("subnet_id", p.SubnetID) d.Set("subnet_id", p.SubnetID)
@ -207,20 +207,24 @@ func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error {
var updateOpts vips.UpdateOpts var updateOpts vips.UpdateOpts
if d.HasChange("name") { if d.HasChange("name") {
updateOpts.Name = d.Get("name").(string) v := d.Get("name").(string)
updateOpts.Name = &v
} }
if d.HasChange("pool_id") { if d.HasChange("pool_id") {
updateOpts.PoolID = d.Get("pool_id").(string) v := d.Get("pool_id").(string)
updateOpts.PoolID = &v
} }
if d.HasChange("description") { if d.HasChange("description") {
updateOpts.Description = d.Get("description").(string) v := d.Get("description").(string)
} updateOpts.Description = &v
if d.HasChange("persistence") {
updateOpts.Persistence = resourceVipPersistenceV1(d)
} }
if d.HasChange("conn_limit") { if d.HasChange("conn_limit") {
updateOpts.ConnLimit = gophercloud.MaybeInt(d.Get("conn_limit").(int)) updateOpts.ConnLimit = gophercloud.MaybeInt(d.Get("conn_limit").(int))
} }
if d.HasChange("floating_ip") { if d.HasChange("floating_ip") {
portID := d.Get("port_id").(string) portID := d.Get("port_id").(string)
@ -240,8 +244,9 @@ func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error {
// If a floating IP is found we unassign it // If a floating IP is found we unassign it
if len(fips) == 1 { if len(fips) == 1 {
portID := ""
updateOpts := floatingips.UpdateOpts{ updateOpts := floatingips.UpdateOpts{
PortID: "", PortID: &portID,
} }
if err = floatingips.Update(networkingClient, fips[0].ID, updateOpts).Err; err != nil { if err = floatingips.Update(networkingClient, fips[0].ID, updateOpts).Err; err != nil {
return err return err
@ -254,11 +259,15 @@ func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error {
lbVipV1AssignFloatingIP(floatingIP, portID, networkingClient) lbVipV1AssignFloatingIP(floatingIP, portID, networkingClient)
} }
} }
if d.HasChange("admin_state_up") { if d.HasChange("admin_state_up") {
asu := d.Get("admin_state_up").(bool) asu := d.Get("admin_state_up").(bool)
updateOpts.AdminStateUp = &asu updateOpts.AdminStateUp = &asu
} }
// Persistence has to be included, even if it hasn't changed.
updateOpts.Persistence = resourceVipPersistenceV1(d)
log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts) log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts)
_, err = vips.Update(networkingClient, d.Id(), updateOpts).Extract() _, err = vips.Update(networkingClient, d.Id(), updateOpts).Extract()
@ -330,7 +339,7 @@ func lbVipV1AssignFloatingIP(floatingIP, portID string, networkingClient *gopher
} }
updateOpts := floatingips.UpdateOpts{ updateOpts := floatingips.UpdateOpts{
PortID: portID, PortID: &portID,
} }
if err = floatingips.Update(networkingClient, fips[0].ID, updateOpts).Err; err != nil { if err = floatingips.Update(networkingClient, fips[0].ID, updateOpts).Err; err != nil {
return err return err
@ -361,27 +370,21 @@ func waitForLBVIPDelete(networkingClient *gophercloud.ServiceClient, vipId strin
p, err := vips.Get(networkingClient, vipId).Extract() p, err := vips.Get(networkingClient, vipId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB VIP %s", vipId) log.Printf("[DEBUG] Successfully deleted OpenStack LB VIP %s", vipId)
return p, "DELETED", nil return p, "DELETED", nil
} }
return p, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LB VIP: %+v", p) log.Printf("[DEBUG] OpenStack LB VIP: %+v", p)
err = vips.Delete(networkingClient, vipId).ExtractErr() err = vips.Delete(networkingClient, vipId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack LB VIP %s", vipId) log.Printf("[DEBUG] Successfully deleted OpenStack LB VIP %s", vipId)
return p, "DELETED", nil return p, "DELETED", nil
} }
return p, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack LB VIP %s still active.", vipId) log.Printf("[DEBUG] OpenStack LB VIP %s still active.", vipId)

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
) )
func TestAccLBV1VIP_basic(t *testing.T) { func TestAccLBV1VIP_basic(t *testing.T) {
@ -152,9 +152,9 @@ var testAccLBV1VIP_update = fmt.Sprintf(`
protocol = "HTTP" protocol = "HTTP"
port = 80 port = 80
pool_id = "${openstack_lb_pool_v1.pool_1.id}" pool_id = "${openstack_lb_pool_v1.pool_1.id}"
admin_state_up = true
persistence { persistence {
type = "SOURCE_IP" type = "SOURCE_IP"
} }
admin_state_up = true
}`, }`,
OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME)

View File

@ -8,10 +8,10 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
func resourceNetworkingFloatingIPV2() *schema.Resource { func resourceNetworkingFloatingIPV2() *schema.Resource {
@ -139,7 +139,8 @@ func resourceNetworkFloatingIPV2Update(d *schema.ResourceData, meta interface{})
var updateOpts floatingips.UpdateOpts var updateOpts floatingips.UpdateOpts
if d.HasChange("port_id") { if d.HasChange("port_id") {
updateOpts.PortID = d.Get("port_id").(string) portID := d.Get("port_id").(string)
updateOpts.PortID = &portID
} }
log.Printf("[DEBUG] Update Options: %#v", updateOpts) log.Printf("[DEBUG] Update Options: %#v", updateOpts)
@ -259,26 +260,20 @@ func waitForFloatingIPDelete(networkingClient *gophercloud.ServiceClient, fId st
f, err := floatingips.Get(networkingClient, fId).Extract() f, err := floatingips.Get(networkingClient, fId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return f, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId) log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId)
return f, "DELETED", nil return f, "DELETED", nil
} }
return f, "ACTIVE", err
} }
err = floatingips.Delete(networkingClient, fId).ExtractErr() err = floatingips.Delete(networkingClient, fId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return f, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId) log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId)
return f, "DELETED", nil return f, "DELETED", nil
} }
return f, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Floating IP %s still active.\n", fId) log.Printf("[DEBUG] OpenStack Floating IP %s still active.\n", fId)

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
) )
func TestAccNetworkingV2FloatingIP_basic(t *testing.T) { func TestAccNetworkingV2FloatingIP_basic(t *testing.T) {

View File

@ -9,8 +9,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
) )
func resourceNetworkingNetworkV2() *schema.Resource { func resourceNetworkingNetworkV2() *schema.Resource {
@ -62,41 +62,6 @@ func resourceNetworkingNetworkV2() *schema.Resource {
} }
} }
// NetworkCreateOpts contains all teh values needed to create a new network.
type NetworkCreateOpts struct {
AdminStateUp *bool
Name string
Shared *bool
TenantID string
ValueSpecs map[string]string
}
// ToNetworkCreateMpa casts a networkCreateOpts struct to a map.
func (opts NetworkCreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
n := make(map[string]interface{})
if opts.AdminStateUp != nil {
n["admin_state_up"] = &opts.AdminStateUp
}
if opts.Name != "" {
n["name"] = opts.Name
}
if opts.Shared != nil {
n["shared"] = &opts.Shared
}
if opts.TenantID != "" {
n["tenant_id"] = opts.TenantID
}
if opts.ValueSpecs != nil {
for k, v := range opts.ValueSpecs {
n[k] = v
}
}
return map[string]interface{}{"network": n}, nil
}
func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) error { func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) networkingClient, err := config.networkingV2Client(d.Get("region").(string))
@ -105,9 +70,11 @@ func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{})
} }
createOpts := NetworkCreateOpts{ createOpts := NetworkCreateOpts{
networks.CreateOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
ValueSpecs: networkValueSpecs(d), },
MapValueSpecs(d),
} }
asuRaw := d.Get("admin_state_up").(string) asuRaw := d.Get("admin_state_up").(string)
@ -165,7 +132,7 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e
return CheckDeleted(d, err, "network") return CheckDeleted(d, err, "network")
} }
log.Printf("[DEBUG] Retreived Network %s: %+v", d.Id(), n) log.Printf("[DEBUG] Retrieved Network %s: %+v", d.Id(), n)
d.Set("name", n.Name) d.Set("name", n.Name)
d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp))
@ -264,37 +231,28 @@ func waitForNetworkDelete(networkingClient *gophercloud.ServiceClient, networkId
n, err := networks.Get(networkingClient, networkId).Extract() n, err := networks.Get(networkingClient, networkId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return n, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId) log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId)
return n, "DELETED", nil return n, "DELETED", nil
} }
return n, "ACTIVE", err
} }
err = networks.Delete(networkingClient, networkId).ExtractErr() err = networks.Delete(networkingClient, networkId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return n, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId) log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId)
return n, "DELETED", nil return n, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
return n, "ACTIVE", nil
}
}
return n, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Network %s still active.\n", networkId) log.Printf("[DEBUG] OpenStack Network %s still active.\n", networkId)
return n, "ACTIVE", nil return n, "ACTIVE", nil
} }
} }
func networkValueSpecs(d *schema.ResourceData) map[string]string {
m := make(map[string]string)
for key, val := range d.Get("value_specs").(map[string]interface{}) {
m[key] = val.(string)
}
return m
}

View File

@ -8,12 +8,12 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func TestAccNetworkingV2Network_basic(t *testing.T) { func TestAccNetworkingV2Network_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
) )
func resourceNetworkingPortV2() *schema.Resource { func resourceNetworkingPortV2() *schema.Resource {
@ -175,7 +175,7 @@ func resourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) erro
return CheckDeleted(d, err, "port") return CheckDeleted(d, err, "port")
} }
log.Printf("[DEBUG] Retreived Port %s: %+v", d.Id(), p) log.Printf("[DEBUG] Retrieved Port %s: %+v", d.Id(), p)
d.Set("name", p.Name) d.Set("name", p.Name)
d.Set("admin_state_up", p.AdminStateUp) d.Set("admin_state_up", p.AdminStateUp)
@ -359,26 +359,20 @@ func waitForNetworkPortDelete(networkingClient *gophercloud.ServiceClient, portI
p, err := ports.Get(networkingClient, portId).Extract() p, err := ports.Get(networkingClient, portId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId) log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId)
return p, "DELETED", nil return p, "DELETED", nil
} }
return p, "ACTIVE", err
} }
err = ports.Delete(networkingClient, portId).ExtractErr() err = ports.Delete(networkingClient, portId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return p, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId) log.Printf("[DEBUG] Successfully deleted OpenStack Port %s", portId)
return p, "DELETED", nil return p, "DELETED", nil
} }
return p, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Port %s still active.\n", portId) log.Printf("[DEBUG] OpenStack Port %s still active.\n", portId)

View File

@ -7,9 +7,9 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func TestAccNetworkingV2Port_basic(t *testing.T) { func TestAccNetworkingV2Port_basic(t *testing.T) {

View File

@ -8,9 +8,9 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
) )
func resourceNetworkingRouterInterfaceV2() *schema.Resource { func resourceNetworkingRouterInterfaceV2() *schema.Resource {
@ -52,7 +52,7 @@ func resourceNetworkingRouterInterfaceV2Create(d *schema.ResourceData, meta inte
return fmt.Errorf("Error creating OpenStack networking client: %s", err) return fmt.Errorf("Error creating OpenStack networking client: %s", err)
} }
createOpts := routers.InterfaceOpts{ createOpts := routers.AddInterfaceOpts{
SubnetID: d.Get("subnet_id").(string), SubnetID: d.Get("subnet_id").(string),
PortID: d.Get("port_id").(string), PortID: d.Get("port_id").(string),
} }
@ -91,19 +91,15 @@ func resourceNetworkingRouterInterfaceV2Read(d *schema.ResourceData, meta interf
n, err := ports.Get(networkingClient, d.Id()).Extract() n, err := ports.Get(networkingClient, d.Id()).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err)
}
if httpError.Actual == 404 {
d.SetId("") d.SetId("")
return nil return nil
} }
return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err) return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err)
} }
log.Printf("[DEBUG] Retreived Router Interface %s: %+v", d.Id(), n) log.Printf("[DEBUG] Retrieved Router Interface %s: %+v", d.Id(), n)
return nil return nil
} }
@ -150,38 +146,39 @@ func waitForRouterInterfaceDelete(networkingClient *gophercloud.ServiceClient, d
routerId := d.Get("router_id").(string) routerId := d.Get("router_id").(string)
routerInterfaceId := d.Id() routerInterfaceId := d.Id()
log.Printf("[DEBUG] Attempting to delete OpenStack Router Interface %s.\n", routerInterfaceId) log.Printf("[DEBUG] Attempting to delete OpenStack Router Interface %s.", routerInterfaceId)
removeOpts := routers.InterfaceOpts{ removeOpts := routers.RemoveInterfaceOpts{
SubnetID: d.Get("subnet_id").(string), SubnetID: d.Get("subnet_id").(string),
PortID: d.Get("port_id").(string), PortID: d.Get("port_id").(string),
} }
r, err := ports.Get(networkingClient, routerInterfaceId).Extract() r, err := ports.Get(networkingClient, routerInterfaceId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s", routerInterfaceId) log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s", routerInterfaceId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
_, err = routers.RemoveInterface(networkingClient, routerId, removeOpts).Extract() _, err = routers.RemoveInterface(networkingClient, routerId, removeOpts).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok { log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s.", routerInterfaceId)
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s", routerInterfaceId)
return r, "DELETED", nil return r, "DELETED", nil
} }
} if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack Router Interface %s still active.\n", routerInterfaceId) log.Printf("[DEBUG] Router Interface %s is still in use.", routerInterfaceId)
return r, "ACTIVE", nil
}
}
return r, "ACTIVE", err
}
log.Printf("[DEBUG] OpenStack Router Interface %s is still active.", routerInterfaceId)
return r, "ACTIVE", nil return r, "ACTIVE", nil
} }
} }

View File

@ -7,10 +7,10 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func TestAccNetworkingV2RouterInterface_basic_subnet(t *testing.T) { func TestAccNetworkingV2RouterInterface_basic_subnet(t *testing.T) {

View File

@ -6,8 +6,8 @@ import (
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
) )
func resourceNetworkingRouterRouteV2() *schema.Resource { func resourceNetworkingRouterRouteV2() *schema.Resource {
@ -59,15 +59,11 @@ func resourceNetworkingRouterRouteV2Create(d *schema.ResourceData, meta interfac
n, err := routers.Get(networkingClient, routerId).Extract() n, err := routers.Get(networkingClient, routerId).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
}
if httpError.Actual == 404 {
d.SetId("") d.SetId("")
return nil return nil
} }
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
} }
@ -121,15 +117,11 @@ func resourceNetworkingRouterRouteV2Read(d *schema.ResourceData, meta interface{
n, err := routers.Get(networkingClient, routerId).Extract() n, err := routers.Get(networkingClient, routerId).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
}
if httpError.Actual == 404 {
d.SetId("") d.SetId("")
return nil return nil
} }
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
} }
@ -168,14 +160,10 @@ func resourceNetworkingRouterRouteV2Delete(d *schema.ResourceData, meta interfac
n, err := routers.Get(networkingClient, routerId).Extract() n, err := routers.Get(networkingClient, routerId).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
}
if httpError.Actual == 404 {
return nil return nil
} }
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
} }

View File

@ -7,9 +7,9 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func TestAccNetworkingV2RouterRoute_basic(t *testing.T) { func TestAccNetworkingV2RouterRoute_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
) )
func resourceNetworkingRouterV2() *schema.Resource { func resourceNetworkingRouterV2() *schema.Resource {
@ -63,50 +63,6 @@ func resourceNetworkingRouterV2() *schema.Resource {
} }
} }
// routerCreateOpts contains all the values needed to create a new router. There are
// no required values.
type RouterCreateOpts struct {
Name string
AdminStateUp *bool
Distributed *bool
TenantID string
GatewayInfo *routers.GatewayInfo
ValueSpecs map[string]string
}
// ToRouterCreateMap casts a routerCreateOpts struct to a map.
func (opts RouterCreateOpts) ToRouterCreateMap() (map[string]interface{}, error) {
r := make(map[string]interface{})
if gophercloud.MaybeString(opts.Name) != nil {
r["name"] = opts.Name
}
if opts.AdminStateUp != nil {
r["admin_state_up"] = opts.AdminStateUp
}
if opts.Distributed != nil {
r["distributed"] = opts.Distributed
}
if gophercloud.MaybeString(opts.TenantID) != nil {
r["tenant_id"] = opts.TenantID
}
if opts.GatewayInfo != nil {
r["external_gateway_info"] = opts.GatewayInfo
}
if opts.ValueSpecs != nil {
for k, v := range opts.ValueSpecs {
r[k] = v
}
}
return map[string]interface{}{"router": r}, nil
}
func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) error { func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) networkingClient, err := config.networkingV2Client(d.Get("region").(string))
@ -115,9 +71,11 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{})
} }
createOpts := RouterCreateOpts{ createOpts := RouterCreateOpts{
routers.CreateOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
ValueSpecs: routerValueSpecs(d), },
MapValueSpecs(d),
} }
if asuRaw, ok := d.GetOk("admin_state_up"); ok { if asuRaw, ok := d.GetOk("admin_state_up"); ok {
@ -171,19 +129,15 @@ func resourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) er
n, err := routers.Get(networkingClient, d.Id()).Extract() n, err := routers.Get(networkingClient, d.Id()).Extract()
if err != nil { if err != nil {
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
}
if httpError.Actual == 404 {
d.SetId("") d.SetId("")
return nil return nil
} }
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
} }
log.Printf("[DEBUG] Retreived Router %s: %+v", d.Id(), n) log.Printf("[DEBUG] Retrieved Router %s: %+v", d.Id(), n)
d.Set("name", n.Name) d.Set("name", n.Name)
d.Set("admin_state_up", n.AdminStateUp) d.Set("admin_state_up", n.AdminStateUp)
@ -276,37 +230,23 @@ func waitForRouterDelete(networkingClient *gophercloud.ServiceClient, routerId s
r, err := routers.Get(networkingClient, routerId).Extract() r, err := routers.Get(networkingClient, routerId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Router %s", routerId) log.Printf("[DEBUG] Successfully deleted OpenStack Router %s", routerId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
err = routers.Delete(networkingClient, routerId).ExtractErr() err = routers.Delete(networkingClient, routerId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Router %s", routerId) log.Printf("[DEBUG] Successfully deleted OpenStack Router %s", routerId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Router %s still active.\n", routerId) log.Printf("[DEBUG] OpenStack Router %s still active.\n", routerId)
return r, "ACTIVE", nil return r, "ACTIVE", nil
} }
} }
func routerValueSpecs(d *schema.ResourceData) map[string]string {
m := make(map[string]string)
for key, val := range d.Get("value_specs").(map[string]interface{}) {
m[key] = val.(string)
}
return m
}

View File

@ -8,7 +8,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
) )
func TestAccNetworkingV2Router_basic(t *testing.T) { func TestAccNetworkingV2Router_basic(t *testing.T) {

View File

@ -9,8 +9,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
) )
func resourceNetworkingSecGroupRuleV2() *schema.Resource { func resourceNetworkingSecGroupRuleV2() *schema.Resource {
@ -106,17 +106,29 @@ func resourceNetworkingSecGroupRuleV2Create(d *schema.ResourceData, meta interfa
} }
opts := rules.CreateOpts{ opts := rules.CreateOpts{
Direction: d.Get("direction").(string),
EtherType: d.Get("ethertype").(string),
SecGroupID: d.Get("security_group_id").(string), SecGroupID: d.Get("security_group_id").(string),
PortRangeMin: d.Get("port_range_min").(int), PortRangeMin: d.Get("port_range_min").(int),
PortRangeMax: d.Get("port_range_max").(int), PortRangeMax: d.Get("port_range_max").(int),
Protocol: d.Get("protocol").(string),
RemoteGroupID: d.Get("remote_group_id").(string), RemoteGroupID: d.Get("remote_group_id").(string),
RemoteIPPrefix: d.Get("remote_ip_prefix").(string), RemoteIPPrefix: d.Get("remote_ip_prefix").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
} }
if v, ok := d.GetOk("direction"); ok {
direction := resourceNetworkingSecGroupRuleV2DetermineDirection(v.(string))
opts.Direction = direction
}
if v, ok := d.GetOk("ethertype"); ok {
ethertype := resourceNetworkingSecGroupRuleV2DetermineEtherType(v.(string))
opts.EtherType = ethertype
}
if v, ok := d.GetOk("protocol"); ok {
protocol := resourceNetworkingSecGroupRuleV2DetermineProtocol(v.(string))
opts.Protocol = protocol
}
log.Printf("[DEBUG] Create OpenStack Neutron security group: %#v", opts) log.Printf("[DEBUG] Create OpenStack Neutron security group: %#v", opts)
security_group_rule, err := rules.Create(networkingClient, opts).Extract() security_group_rule, err := rules.Create(networkingClient, opts).Extract()
@ -185,32 +197,64 @@ func resourceNetworkingSecGroupRuleV2Delete(d *schema.ResourceData, meta interfa
return err return err
} }
func resourceNetworkingSecGroupRuleV2DetermineDirection(v string) rules.RuleDirection {
var direction rules.RuleDirection
switch v {
case "ingress":
direction = rules.DirIngress
case "egress":
direction = rules.DirEgress
}
return direction
}
func resourceNetworkingSecGroupRuleV2DetermineEtherType(v string) rules.RuleEtherType {
var etherType rules.RuleEtherType
switch v {
case "IPv4":
etherType = rules.EtherType4
case "IPv6":
etherType = rules.EtherType6
}
return etherType
}
func resourceNetworkingSecGroupRuleV2DetermineProtocol(v string) rules.RuleProtocol {
var protocol rules.RuleProtocol
switch v {
case "tcp":
protocol = rules.ProtocolTCP
case "udp":
protocol = rules.ProtocolUDP
case "icmp":
protocol = rules.ProtocolICMP
}
return protocol
}
func waitForSecGroupRuleDelete(networkingClient *gophercloud.ServiceClient, secGroupRuleId string) resource.StateRefreshFunc { func waitForSecGroupRuleDelete(networkingClient *gophercloud.ServiceClient, secGroupRuleId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
log.Printf("[DEBUG] Attempting to delete OpenStack Security Group Rule %s.\n", secGroupRuleId) log.Printf("[DEBUG] Attempting to delete OpenStack Security Group Rule %s.\n", secGroupRuleId)
r, err := rules.Get(networkingClient, secGroupRuleId).Extract() r, err := rules.Get(networkingClient, secGroupRuleId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group Rule %s", secGroupRuleId) log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group Rule %s", secGroupRuleId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
err = rules.Delete(networkingClient, secGroupRuleId).ExtractErr() err = rules.Delete(networkingClient, secGroupRuleId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group Rule %s", secGroupRuleId) log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group Rule %s", secGroupRuleId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Neutron Security Group Rule %s still active.\n", secGroupRuleId) log.Printf("[DEBUG] OpenStack Neutron Security Group Rule %s still active.\n", secGroupRuleId)

View File

@ -7,8 +7,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
) )
func TestAccNetworkingV2SecGroupRule_basic(t *testing.T) { func TestAccNetworkingV2SecGroupRule_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
) )
func resourceNetworkingSecGroupV2() *schema.Resource { func resourceNetworkingSecGroupV2() *schema.Resource {
@ -131,26 +131,20 @@ func waitForSecGroupDelete(networkingClient *gophercloud.ServiceClient, secGroup
r, err := groups.Get(networkingClient, secGroupId).Extract() r, err := groups.Get(networkingClient, secGroupId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group %s", secGroupId) log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group %s", secGroupId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
err = groups.Delete(networkingClient, secGroupId).ExtractErr() err = groups.Delete(networkingClient, secGroupId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return r, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group %s", secGroupId) log.Printf("[DEBUG] Successfully deleted OpenStack Neutron Security Group %s", secGroupId)
return r, "DELETED", nil return r, "DELETED", nil
} }
return r, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Neutron Security Group %s still active.\n", secGroupId) log.Printf("[DEBUG] OpenStack Neutron Security Group %s still active.\n", secGroupId)

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
) )
func TestAccNetworkingV2SecGroup_basic(t *testing.T) { func TestAccNetworkingV2SecGroup_basic(t *testing.T) {

View File

@ -8,8 +8,8 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func resourceNetworkingSubnetV2() *schema.Resource { func resourceNetworkingSubnetV2() *schema.Resource {
@ -123,82 +123,6 @@ func resourceNetworkingSubnetV2() *schema.Resource {
} }
} }
// SubnetCreateOpts represents the attributes used when creating a new subnet.
type SubnetCreateOpts struct {
// Required
NetworkID string
CIDR string
// Optional
Name string
TenantID string
AllocationPools []subnets.AllocationPool
GatewayIP string
NoGateway bool
IPVersion int
EnableDHCP *bool
DNSNameservers []string
HostRoutes []subnets.HostRoute
ValueSpecs map[string]string
}
// ToSubnetCreateMap casts a CreateOpts struct to a map.
func (opts SubnetCreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
s := make(map[string]interface{})
if opts.NetworkID == "" {
return nil, fmt.Errorf("A network ID is required")
}
if opts.CIDR == "" {
return nil, fmt.Errorf("A valid CIDR is required")
}
if opts.IPVersion != 0 && opts.IPVersion != subnets.IPv4 && opts.IPVersion != subnets.IPv6 {
return nil, fmt.Errorf("An IP type must either be 4 or 6")
}
// Both GatewayIP and NoGateway should not be set
if opts.GatewayIP != "" && opts.NoGateway {
return nil, fmt.Errorf("Both disabling the gateway and specifying a gateway is not allowed")
}
s["network_id"] = opts.NetworkID
s["cidr"] = opts.CIDR
if opts.EnableDHCP != nil {
s["enable_dhcp"] = &opts.EnableDHCP
}
if opts.Name != "" {
s["name"] = opts.Name
}
if opts.GatewayIP != "" {
s["gateway_ip"] = opts.GatewayIP
} else if opts.NoGateway {
s["gateway_ip"] = nil
}
if opts.TenantID != "" {
s["tenant_id"] = opts.TenantID
}
if opts.IPVersion != 0 {
s["ip_version"] = opts.IPVersion
}
if len(opts.AllocationPools) != 0 {
s["allocation_pools"] = opts.AllocationPools
}
if len(opts.DNSNameservers) != 0 {
s["dns_nameservers"] = opts.DNSNameservers
}
if len(opts.HostRoutes) != 0 {
s["host_routes"] = opts.HostRoutes
}
if opts.ValueSpecs != nil {
for k, v := range opts.ValueSpecs {
s[k] = v
}
}
return map[string]interface{}{"subnet": s}, nil
}
func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) error { func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) networkingClient, err := config.networkingV2Client(d.Get("region").(string))
@ -206,35 +130,48 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{})
return fmt.Errorf("Error creating OpenStack networking client: %s", err) return fmt.Errorf("Error creating OpenStack networking client: %s", err)
} }
if _, ok := d.GetOk("gateway_ip"); ok {
if _, ok2 := d.GetOk("no_gateway"); ok2 {
return fmt.Errorf("Both gateway_ip and no_gateway cannot be set.")
}
}
enableDHCP := d.Get("enable_dhcp").(bool)
createOpts := SubnetCreateOpts{ createOpts := SubnetCreateOpts{
subnets.CreateOpts{
NetworkID: d.Get("network_id").(string), NetworkID: d.Get("network_id").(string),
CIDR: d.Get("cidr").(string), CIDR: d.Get("cidr").(string),
Name: d.Get("name").(string), Name: d.Get("name").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
AllocationPools: resourceSubnetAllocationPoolsV2(d), AllocationPools: resourceSubnetAllocationPoolsV2(d),
GatewayIP: d.Get("gateway_ip").(string),
NoGateway: d.Get("no_gateway").(bool),
IPVersion: d.Get("ip_version").(int),
DNSNameservers: resourceSubnetDNSNameserversV2(d), DNSNameservers: resourceSubnetDNSNameserversV2(d),
HostRoutes: resourceSubnetHostRoutesV2(d), HostRoutes: resourceSubnetHostRoutesV2(d),
EnableDHCP: &enableDHCP, EnableDHCP: nil,
ValueSpecs: subnetValueSpecs(d), },
MapValueSpecs(d),
}
noGateway := d.Get("no_gateway").(bool)
gatewayIP := d.Get("gateway_ip").(string)
if gatewayIP != "" && noGateway {
return fmt.Errorf("Both gateway_ip and no_gateway cannot be set")
}
if gatewayIP != "" {
createOpts.GatewayIP = &gatewayIP
}
if noGateway {
disableGateway := ""
createOpts.GatewayIP = &disableGateway
}
enableDHCP := d.Get("enable_dhcp").(bool)
createOpts.EnableDHCP = &enableDHCP
if v, ok := d.GetOk("ip_version"); ok {
ipVersion := resourceNetworkingSubnetV2DetermineIPVersion(v.(int))
createOpts.IPVersion = ipVersion
} }
log.Printf("[DEBUG] Create Options: %#v", createOpts)
s, err := subnets.Create(networkingClient, createOpts).Extract() s, err := subnets.Create(networkingClient, createOpts).Extract()
if err != nil { if err != nil {
return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err) return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err)
} }
log.Printf("[INFO] Subnet ID: %s", s.ID)
log.Printf("[DEBUG] Waiting for Subnet (%s) to become available", s.ID) log.Printf("[DEBUG] Waiting for Subnet (%s) to become available", s.ID)
stateConf := &resource.StateChangeConf{ stateConf := &resource.StateChangeConf{
@ -249,6 +186,7 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{})
d.SetId(s.ID) d.SetId(s.ID)
log.Printf("[DEBUG] Created Subnet %s: %#v", s.ID, s)
return resourceNetworkingSubnetV2Read(d, meta) return resourceNetworkingSubnetV2Read(d, meta)
} }
@ -264,9 +202,9 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er
return CheckDeleted(d, err, "subnet") return CheckDeleted(d, err, "subnet")
} }
log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s) log.Printf("[DEBUG] Retrieved Subnet %s: %#v", d.Id(), s)
d.Set("newtork_id", s.NetworkID) d.Set("network_id", s.NetworkID)
d.Set("cidr", s.CIDR) d.Set("cidr", s.CIDR)
d.Set("ip_version", s.IPVersion) d.Set("ip_version", s.IPVersion)
d.Set("name", s.Name) d.Set("name", s.Name)
@ -290,23 +228,38 @@ func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{})
// Check if both gateway_ip and no_gateway are set // Check if both gateway_ip and no_gateway are set
if _, ok := d.GetOk("gateway_ip"); ok { if _, ok := d.GetOk("gateway_ip"); ok {
if _, ok2 := d.GetOk("no_gateway"); ok2 { noGateway := d.Get("no_gateway").(bool)
if noGateway {
return fmt.Errorf("Both gateway_ip and no_gateway cannot be set.") return fmt.Errorf("Both gateway_ip and no_gateway cannot be set.")
} }
} }
var updateOpts subnets.UpdateOpts var updateOpts subnets.UpdateOpts
noGateway := d.Get("no_gateway").(bool)
gatewayIP := d.Get("gateway_ip").(string)
if gatewayIP != "" && noGateway {
return fmt.Errorf("Both gateway_ip and no_gateway cannot be set")
}
if d.HasChange("name") { if d.HasChange("name") {
updateOpts.Name = d.Get("name").(string) updateOpts.Name = d.Get("name").(string)
} }
if d.HasChange("gateway_ip") { if d.HasChange("gateway_ip") {
updateOpts.GatewayIP = d.Get("gateway_ip").(string) updateOpts.GatewayIP = nil
if v, ok := d.GetOk("gateway_ip"); ok {
gatewayIP := v.(string)
updateOpts.GatewayIP = &gatewayIP
}
} }
if d.HasChange("no_gateway") { if d.HasChange("no_gateway") {
updateOpts.NoGateway = d.Get("no_gateway").(bool) if d.Get("no_gateway").(bool) {
gatewayIP := ""
updateOpts.GatewayIP = &gatewayIP
}
} }
if d.HasChange("dns_nameservers") { if d.HasChange("dns_nameservers") {
@ -392,6 +345,18 @@ func resourceSubnetHostRoutesV2(d *schema.ResourceData) []subnets.HostRoute {
return hr return hr
} }
func resourceNetworkingSubnetV2DetermineIPVersion(v int) gophercloud.IPVersion {
var ipVersion gophercloud.IPVersion
switch v {
case 4:
ipVersion = gophercloud.IPv4
case 6:
ipVersion = gophercloud.IPv6
}
return ipVersion
}
func waitForSubnetActive(networkingClient *gophercloud.ServiceClient, subnetId string) resource.StateRefreshFunc { func waitForSubnetActive(networkingClient *gophercloud.ServiceClient, subnetId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) { return func() (interface{}, string, error) {
s, err := subnets.Get(networkingClient, subnetId).Extract() s, err := subnets.Get(networkingClient, subnetId).Extract()
@ -410,37 +375,28 @@ func waitForSubnetDelete(networkingClient *gophercloud.ServiceClient, subnetId s
s, err := subnets.Get(networkingClient, subnetId).Extract() s, err := subnets.Get(networkingClient, subnetId).Extract()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return s, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId) log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId)
return s, "DELETED", nil return s, "DELETED", nil
} }
return s, "ACTIVE", err
} }
err = subnets.Delete(networkingClient, subnetId).ExtractErr() err = subnets.Delete(networkingClient, subnetId).ExtractErr()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return s, "ACTIVE", err
}
if errCode.Actual == 404 {
log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId) log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId)
return s, "DELETED", nil return s, "DELETED", nil
} }
if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
if errCode.Actual == 409 {
return s, "ACTIVE", nil
}
}
return s, "ACTIVE", err
} }
log.Printf("[DEBUG] OpenStack Subnet %s still active.\n", subnetId) log.Printf("[DEBUG] OpenStack Subnet %s still active.\n", subnetId)
return s, "ACTIVE", nil return s, "ACTIVE", nil
} }
} }
func subnetValueSpecs(d *schema.ResourceData) map[string]string {
m := make(map[string]string)
for key, val := range d.Get("value_specs").(map[string]interface{}) {
m[key] = val.(string)
}
return m
}

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
) )
func TestAccNetworkingV2Subnet_basic(t *testing.T) { func TestAccNetworkingV2Subnet_basic(t *testing.T) {
@ -27,7 +27,7 @@ func TestAccNetworkingV2Subnet_basic(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccNetworkingV2Subnet_update, Config: testAccNetworkingV2Subnet_update,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "name", "tf-test-subnet"), resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "name", "subnet_1"),
resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "gateway_ip", "192.168.199.1"), resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "gateway_ip", "192.168.199.1"),
resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "enable_dhcp", "true"), resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "enable_dhcp", "true"),
), ),
@ -174,6 +174,10 @@ var testAccNetworkingV2Subnet_basic = fmt.Sprintf(`
resource "openstack_networking_subnet_v2" "subnet_1" { resource "openstack_networking_subnet_v2" "subnet_1" {
network_id = "${openstack_networking_network_v2.network_1.id}" network_id = "${openstack_networking_network_v2.network_1.id}"
cidr = "192.168.199.0/24" cidr = "192.168.199.0/24"
allocation_pools {
start = "192.168.199.100"
end = "192.168.199.200"
}
}`) }`)
var testAccNetworkingV2Subnet_update = fmt.Sprintf(` var testAccNetworkingV2Subnet_update = fmt.Sprintf(`
@ -183,10 +187,14 @@ var testAccNetworkingV2Subnet_update = fmt.Sprintf(`
} }
resource "openstack_networking_subnet_v2" "subnet_1" { resource "openstack_networking_subnet_v2" "subnet_1" {
name = "tf-test-subnet" name = "subnet_1"
network_id = "${openstack_networking_network_v2.network_1.id}" network_id = "${openstack_networking_network_v2.network_1.id}"
cidr = "192.168.199.0/24" cidr = "192.168.199.0/24"
gateway_ip = "192.168.199.1" gateway_ip = "192.168.199.1"
allocation_pools {
start = "192.168.199.100"
end = "192.168.199.200"
}
}`) }`)
var testAccNetworkingV2Subnet_enableDHCP = fmt.Sprintf(` var testAccNetworkingV2Subnet_enableDHCP = fmt.Sprintf(`

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
) )
func resourceObjectStorageContainerV1() *schema.Resource { func resourceObjectStorageContainerV1() *schema.Resource {

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
) )
func TestAccObjectStorageV1Container_basic(t *testing.T) { func TestAccObjectStorageV1Container_basic(t *testing.T) {

View File

@ -0,0 +1,52 @@
package openstack
import (
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
)
// NetworkCreateOpts represents the attributes used when creating a new network.
type NetworkCreateOpts struct {
networks.CreateOpts
ValueSpecs map[string]string `json:"value_specs,omitempty"`
}
// ToNetworkCreateMap casts a CreateOpts struct to a map.
// It overrides networks.ToNetworkCreateMap to add the ValueSpecs field.
func (opts NetworkCreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
return BuildRequest(opts, "network")
}
// RouterCreateOpts represents the attributes used when creating a new router.
type RouterCreateOpts struct {
routers.CreateOpts
ValueSpecs map[string]string `json:"value_specs,omitempty"`
}
// ToRouterCreateMap casts a CreateOpts struct to a map.
// It overrides routers.ToRouterCreateMap to add the ValueSpecs field.
func (opts RouterCreateOpts) ToRouterCreateMap() (map[string]interface{}, error) {
return BuildRequest(opts, "router")
}
// SubnetCreateOpts represents the attributes used when creating a new subnet.
type SubnetCreateOpts struct {
subnets.CreateOpts
ValueSpecs map[string]string `json:"value_specs,omitempty"`
}
// ToSubnetCreateMap casts a CreateOpts struct to a map.
// It overrides subnets.ToSubnetCreateMap to add the ValueSpecs field.
func (opts SubnetCreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
b, err := BuildRequest(opts, "subnet")
if err != nil {
return nil, err
}
if m := b["subnet"].(map[string]interface{}); m["gateway_ip"] == "" {
m["gateway_ip"] = nil
}
return b, nil
}

View File

@ -3,20 +3,44 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/gophercloud/gophercloud"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud"
) )
// CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so, // CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so,
// sets the resource ID to the empty string instead of throwing an error. // sets the resource ID to the empty string instead of throwing an error.
func CheckDeleted(d *schema.ResourceData, err error, msg string) error { func CheckDeleted(d *schema.ResourceData, err error, msg string) error {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if _, ok := err.(gophercloud.ErrDefault404); ok {
if !ok {
return fmt.Errorf("%s: %s", msg, err)
}
if errCode.Actual == 404 {
d.SetId("") d.SetId("")
return nil return nil
} }
return fmt.Errorf("%s: %s", msg, err) return fmt.Errorf("%s: %s", msg, err)
} }
// MapValueSpecs converts ResourceData into a map
func MapValueSpecs(d *schema.ResourceData) map[string]string {
m := make(map[string]string)
for key, val := range d.Get("value_specs").(map[string]interface{}) {
m[key] = val.(string)
}
return m
}
// BuildRequest takes an opts struct and builds a request body for
// Gophercloud to execute
func BuildRequest(opts interface{}, parent string) (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
if b["value_specs"] != nil {
for k, v := range b["value_specs"].(map[string]interface{}) {
b[k] = v
}
delete(b, "value_specs")
}
return map[string]interface{}{parent: b}, nil
}

View File

View File

@ -0,0 +1,235 @@
# Contributing to Gophercloud
- [Getting started](#getting-started)
- [Tests](#tests)
- [Style guide](#basic-style-guide)
- [3 ways to get involved](#5-ways-to-get-involved)
## Setting up your git workspace
As a contributor you will need to setup your workspace in a slightly different
way than just downloading it. Here are the basic installation instructions:
1. Configure your `$GOPATH` and run `go get` as described in the main
[README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
get dependencies for unit and acceptance tests.
```bash
go get -tags "fixtures acceptance" github.com/gophercloud/gophercloud
```
2. Move into the directory that houses your local repository:
```bash
cd ${GOPATH}/src/github.com/gophercloud/gophercloud
```
3. Fork the `gophercloud/gophercloud` repository and update your remote refs. You
will need to rename the `origin` remote branch to `upstream`, and add your
fork as `origin` instead:
```bash
git remote rename origin upstream
git remote add origin git@github.com:<my_username>/gophercloud.git
```
4. Checkout the latest development branch:
```bash
git checkout master
```
5. If you're working on something (discussed more in detail below), you will
need to checkout a new feature branch:
```bash
git checkout -b my-new-feature
```
Another thing to bear in mind is that you will need to add a few extra
environment variables for acceptance tests - this is documented in our
[acceptance tests readme](/acceptance).
## Tests
When working on a new or existing feature, testing will be the backbone of your
work since it helps uncover and prevent regressions in the codebase. There are
two types of test we use in Gophercloud: unit tests and acceptance tests, which
are both described below.
### Unit tests
Unit tests are the fine-grained tests that establish and ensure the behavior
of individual units of functionality. We usually test on an
operation-by-operation basis (an operation typically being an API action) with
the use of mocking to set up explicit expectations. Each operation will set up
its HTTP response expectation, and then test how the system responds when fed
this controlled, pre-determined input.
To make life easier, we've introduced a bunch of test helpers to simplify the
process of testing expectations with assertions:
```go
import (
"testing"
"github.com/gophercloud/gophercloud/testhelper"
)
func TestSomething(t *testing.T) {
result, err := Operation()
testhelper.AssertEquals(t, "foo", result.Bar)
testhelper.AssertNoErr(t, err)
}
func TestSomethingElse(t *testing.T) {
testhelper.CheckEquals(t, "expected", "actual")
}
```
`AssertEquals` and `AssertNoErr` will throw a fatal error if a value does not
match an expected value or if an error has been declared, respectively. You can
also use `CheckEquals` and `CheckNoErr` for the same purpose; the only difference
being that `t.Errorf` is raised rather than `t.Fatalf`.
Here is a truncated example of mocked HTTP responses:
```go
import (
"testing"
th "github.com/gophercloud/gophercloud/testhelper"
fake "github.com/gophercloud/gophercloud/testhelper/client"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
)
func TestGet(t *testing.T) {
// Setup the HTTP request multiplexer and server
th.SetupHTTP()
defer th.TeardownHTTP()
th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
// Test we're using the correct HTTP method
th.TestMethod(t, r, "GET")
// Test we're setting the auth token
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
// Set the appropriate headers for our mocked response
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Set the HTTP body
fmt.Fprintf(w, `
{
"network": {
"status": "ACTIVE",
"name": "private-network",
"admin_state_up": true,
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
"shared": true,
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
}
}
`)
})
// Call our API operation
network, err := networks.Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
// Assert no errors and equality
th.AssertNoErr(t, err)
th.AssertEquals(t, n.Status, "ACTIVE")
}
```
### Acceptance tests
As we've already mentioned, unit tests have a very narrow and confined focus -
they test small units of behavior. Acceptance tests on the other hand have a
far larger scope: they are fully functional tests that test the entire API of a
service in one fell swoop. They don't care about unit isolation or mocking
expectations, they instead do a full run-through and consequently test how the
entire system _integrates_ together. When an API satisfies expectations, it
proves by default that the requirements for a contract have been met.
Please be aware that acceptance tests will hit a live API - and may incur
service charges from your provider. Although most tests handle their own
teardown procedures, it is always worth manually checking that resources are
deleted after the test suite finishes.
### Running tests
To run all tests:
```bash
go test -tags fixtures ./...
```
To run all tests with verbose output:
```bash
go test -v -tags fixtures ./...
```
To run tests that match certain [build tags]():
```bash
go test -tags "fixtures foo bar" ./...
```
To run tests for a particular sub-package:
```bash
cd ./path/to/package && go test -tags fixtures .
```
## Style guide
See [here](/STYLEGUIDE.md)
## 3 ways to get involved
There are five main ways you can get involved in our open-source project, and
each is described briefly below. Once you've made up your mind and decided on
your fix, you will need to follow the same basic steps that all submissions are
required to adhere to:
1. [fork](https://help.github.com/articles/fork-a-repo/) the `gophercloud/gophercloud` repository
2. checkout a [new branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)
3. submit your branch as a [pull request](https://help.github.com/articles/creating-a-pull-request/)
### 1. Fixing bugs
If you want to start fixing open bugs, we'd really appreciate that! Bug fixing
is central to any project. The best way to get started is by heading to our
[bug tracker](https://github.com/gophercloud/gophercloud/issues) and finding open
bugs that you think nobody is working on. It might be useful to comment on the
thread to see the current state of the issue and if anybody has made any
breakthroughs on it so far.
### 2. Improving documentation
The best source of documentation is on [godoc.org](http://godoc.org). It is
automatically generated from the source code.
If you feel that a certain section could be improved - whether it's to clarify
ambiguity, correct a technical mistake, or to fix a grammatical error - please
feel entitled to do so! We welcome doc pull requests with the same childlike
enthusiasm as any other contribution!
###3. Working on a new feature
If you've found something we've left out, definitely feel free to start work on
introducing that feature. It's always useful to open an issue or submit a pull
request early on to indicate your intent to a core contributor - this enables
quick/early feedback and can help steer you in the right direction by avoiding
known issues. It might also help you avoid losing time implementing something
that might not ever work. One tip is to prefix your Pull Request issue title
with [wip] - then people know it's a work in progress.
You must ensure that all of your work is well tested - both in terms of unit
and acceptance tests. Untested code will not be merged because it introduces
too much of a risk to end-users.
Happy hacking!

191
vendor/github.com/gophercloud/gophercloud/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Copyright 2012-2013 Rackspace, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

32
vendor/github.com/gophercloud/gophercloud/MIGRATING.md generated vendored Normal file
View File

@ -0,0 +1,32 @@
# Compute
## Floating IPs
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
## Security Groups
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
## Servers
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
```golang
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
res := servers.Reboot(client, server.ID, rebootOpts)
```
# Identity
## V3
### Tokens
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
`time.Time`

139
vendor/github.com/gophercloud/gophercloud/README.md generated vendored Normal file
View File

@ -0,0 +1,139 @@
# Gophercloud: an OpenStack SDK for Go
[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud)
[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master)
Gophercloud is an OpenStack Go SDK.
## Useful links
* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud)
* [Effective Go](https://golang.org/doc/effective_go.html)
## How to install
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
is pointing to an appropriate directory where you want to install Gophercloud:
```bash
mkdir $HOME/go
export GOPATH=$HOME/go
```
To protect yourself against changes in your dependencies, we highly recommend choosing a
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
Gophercloud as a dependency like so:
```bash
go get github.com/gophercloud/gophercloud
# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud"
godep save ./...
```
This will install all the source files you need into a `Godeps/_workspace` directory, which is
referenceable from your own source files when you use the `godep go` command.
## Getting started
### Credentials
Because you'll be hitting an API, you will need to retrieve your OpenStack
credentials and either store them as environment variables or in your local Go
files. The first method is recommended because it decouples credential
information from source code, allowing you to push the latter to your version
control system without any security risk.
You will need to retrieve the following:
* username
* password
* a valid Keystone identity URL
For users that have the OpenStack dashboard installed, there's a shortcut. If
you visit the `project/access_and_security` path in Horizon and click on the
"Download OpenStack RC File" button at the top right hand corner, you will
download a bash file that exports all of your access details to environment
variables. To execute the file, run `source admin-openrc.sh` and you will be
prompted for your password.
### Authentication
Once you have access to your credentials, you can begin plugging them into
Gophercloud. The next step is authentication, and this is handled by a base
"Provider" struct. To get one, you can either pass in your credentials
explicitly, or tell Gophercloud to use environment variables:
```go
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/utils"
)
// Option 1: Pass in the values yourself
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
}
// Option 2: Use a utility function to retrieve all your environment variables
opts, err := openstack.AuthOptionsFromEnv()
```
Once you have the `opts` variable, you can pass it in and get back a
`ProviderClient` struct:
```go
provider, err := openstack.AuthenticatedClient(opts)
```
The `ProviderClient` is the top-level client that all of your OpenStack services
derive from. The provider contains all of the authentication details that allow
your Go code to access the API - such as the base URL and token ID.
### Provision a server
Once we have a base Provider, we inject it as a dependency into each OpenStack
service. In order to work with the Compute API, we need a Compute service
client; which can be created like so:
```go
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
```
We then use this `client` for any Compute API operation we want. In our case,
we want to provision a new server - so we invoke the `Create` method and pass
in the flavor ID (hardware specification) and image ID (operating system) we're
interested in:
```go
import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
server, err := servers.Create(client, servers.CreateOpts{
Name: "My new server!",
FlavorRef: "flavor_id",
ImageRef: "image_id",
}).Extract()
```
The above code sample creates a new server with the parameters, and embodies the
new resource in the `server` variable (a
[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct).
## Backwards-Compatibility Guarantees
None. Vendor it and write tests covering the parts you use.
## Contributing
See the [contributing guide](./CONTRIBUTING.md).
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues).

View File

@ -0,0 +1,68 @@
## On Pull Requests
- Before you start a PR there needs to be a Github issue and a discussion about it
on that issue with a core contributor, even if it's just a 'SGTM'.
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
- A PR's description must contain link(s) to the line(s) in the OpenStack
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
are not good enough. The link(s) should be to a non-`master` branch. For example,
a pull request implementing the creation of a Neutron v2 subnet might put the
following link in the description:
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
From that link, a reviewer (or user) can verify the fields in the request/response
objects in the PR.
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
- A PR should be small. Even if you intend on implementing an entire
service, a PR should only be one route of that service
(e.g. create server or get server, but not both).
- Unless explicitly asked, do not squash commits in the middle of a review; only
append. It makes it difficult for the reviewer to see what's changed from one
review to the next.
## On Code
- In re design: follow as closely as is reasonable the code already in the library.
Most operations (e.g. create, delete) admit the same design.
- Unit tests and acceptance (integration) tests must be written to cover each PR.
Tests for operations with several options (e.g. list, create) should include all
the options in the tests. This will allow users to verify an operation on their
own infrastructure and see an example of usage.
- If in doubt, ask in-line on the PR.
### File Structure
- The following should be used in most cases:
- `requests.go`: contains all the functions that make HTTP requests and the
types associated with the HTTP request (parameters for URL, body, etc)
- `results.go`: contains all the response objects and their methods
- `urls.go`: contains the endpoints to which the requests are made
### Naming
- For methods on a type in `response.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a
`pagination.Pager`, should be named returns of the name `r`.
- Functions in `requests.go` that accept request bodies should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Map`
(eg `ToPortCreateMap`).
- Functions in `requests.go` that accept query strings should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Query`
(eg `ToServerListQuery`).

View File

@ -0,0 +1,331 @@
package gophercloud
/*
AuthOptions stores information needed to authenticate to an OpenStack cluster.
You can populate one manually, or use a provider's AuthOptionsFromEnv() function
to read relevant information from the standard environment variables. Pass one
to a provider's AuthenticatedClient function to authenticate and obtain a
ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and
provider.
*/
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's
// control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed.
Username string `json:"username,omitempty"`
UserID string `json:"id,omitempty"`
Password string `json:"password,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"`
DomainName string `json:"name,omitempty"`
// The TenantID and TenantName fields are optional for the Identity V2 API.
// Some providers allow you to specify a TenantName instead of the TenantId.
// Some require both. Your provider's authentication policies will determine
// how these fields influence authentication.
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
// AllowReauth should be set to true if you grant permission for Gophercloud to
// cache your credentials in memory, and to allow Gophercloud to attempt to
// re-authenticate automatically if/when your token expires. If you set it to
// false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false.
//
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
AllowReauth bool `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
}
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v2 tokens package
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
// Populate the request map.
authMap := make(map[string]interface{})
if opts.Username != "" {
if opts.Password != "" {
authMap["passwordCredentials"] = map[string]interface{}{
"username": opts.Username,
"password": opts.Password,
}
} else {
return nil, ErrMissingInput{Argument: "Password"}
}
} else if opts.TokenID != "" {
authMap["token"] = map[string]interface{}{
"id": opts.TokenID,
}
} else {
return nil, ErrMissingInput{Argument: "Username"}
}
if opts.TenantID != "" {
authMap["tenantId"] = opts.TenantID
}
if opts.TenantName != "" {
authMap["tenantName"] = opts.TenantName
}
return map[string]interface{}{"auth": authMap}, nil
}
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
type domainReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
}
type projectReq struct {
Domain *domainReq `json:"domain,omitempty"`
Name *string `json:"name,omitempty"`
ID *string `json:"id,omitempty"`
}
type userReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Password string `json:"password"`
Domain *domainReq `json:"domain,omitempty"`
}
type passwordReq struct {
User userReq `json:"user"`
}
type tokenReq struct {
ID string `json:"id"`
}
type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
}
type authReq struct {
Identity identityReq `json:"identity"`
}
type request struct {
Auth authReq `json:"auth"`
}
// Populate the request structure based on the provided arguments. Create and return an error
// if insufficient or incompatible information is present.
var req request
// Test first for unrecognized arguments.
if opts.TenantID != "" {
return nil, ErrTenantIDProvided{}
}
if opts.TenantName != "" {
return nil, ErrTenantNameProvided{}
}
if opts.Password == "" {
if opts.TokenID != "" {
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters.
if opts.Username != "" {
return nil, ErrUsernameWithToken{}
}
if opts.UserID != "" {
return nil, ErrUserIDWithToken{}
}
if opts.DomainID != "" {
return nil, ErrDomainIDWithToken{}
}
if opts.DomainName != "" {
return nil, ErrDomainNameWithToken{}
}
// Configure the request for Token authentication.
req.Auth.Identity.Methods = []string{"token"}
req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID,
}
} else {
// If no password or token ID are available, authentication can't continue.
return nil, ErrMissingPassword{}
}
} else {
// Password authentication.
req.Auth.Identity.Methods = []string{"password"}
// At least one of Username and UserID must be specified.
if opts.Username == "" && opts.UserID == "" {
return nil, ErrUsernameOrUserID{}
}
if opts.Username != "" {
// If Username is provided, UserID may not be provided.
if opts.UserID != "" {
return nil, ErrUsernameOrUserID{}
}
// Either DomainID or DomainName must also be specified.
if opts.DomainID == "" && opts.DomainName == "" {
return nil, ErrDomainIDOrDomainName{}
}
if opts.DomainID != "" {
if opts.DomainName != "" {
return nil, ErrDomainIDOrDomainName{}
}
// Configure the request for Username and Password authentication with a DomainID.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
Password: opts.Password,
Domain: &domainReq{ID: &opts.DomainID},
},
}
}
if opts.DomainName != "" {
// Configure the request for Username and Password authentication with a DomainName.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
Password: opts.Password,
Domain: &domainReq{Name: &opts.DomainName},
},
}
}
}
if opts.UserID != "" {
// If UserID is specified, neither DomainID nor DomainName may be.
if opts.DomainID != "" {
return nil, ErrDomainIDWithUserID{}
}
if opts.DomainName != "" {
return nil, ErrDomainNameWithUserID{}
}
// Configure the request for UserID and Password authentication.
req.Auth.Identity.Password = &passwordReq{
User: userReq{ID: &opts.UserID, Password: opts.Password},
}
}
}
b, err := BuildRequestBody(req, "")
if err != nil {
return nil, err
}
if len(scope) != 0 {
b["auth"].(map[string]interface{})["scope"] = scope
}
return b, nil
}
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
var scope struct {
ProjectID string
ProjectName string
DomainID string
DomainName string
}
if opts.TenantID != "" {
scope.ProjectID = opts.TenantID
opts.TenantID = ""
opts.TenantName = ""
} else {
if opts.TenantName != "" {
scope.ProjectName = opts.TenantName
scope.DomainID = opts.DomainID
scope.DomainName = opts.DomainName
}
opts.TenantName = ""
}
if scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if scope.DomainID == "" && scope.DomainName == "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
if scope.ProjectID != "" {
return nil, ErrScopeProjectIDOrProjectName{}
}
if scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"id": &scope.DomainID},
},
}, nil
}
if scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"name": &scope.DomainName},
},
}, nil
}
} else if scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if scope.DomainID != "" {
return nil, ErrScopeProjectIDAlone{}
}
if scope.DomainName != "" {
return nil, ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &scope.ProjectID,
},
}, nil
} else if scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if scope.DomainName != "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &scope.DomainID,
},
}, nil
} else if scope.DomainName != "" {
return nil, ErrScopeDomainName{}
}
return nil, nil
}
func (opts AuthOptions) CanReauth() bool {
return opts.AllowReauth
}

67
vendor/github.com/gophercloud/gophercloud/doc.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
/*
Package gophercloud provides a multi-vendor interface to OpenStack-compatible
clouds. The library has a three-level hierarchy: providers, services, and
resources.
Provider structs represent the service providers that offer and manage a
collection of services. Examples of providers include: OpenStack, Rackspace,
HP. These are defined like so:
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
provider, err := openstack.AuthenticatedClient(opts)
Service structs are specific to a provider and handle all of the logic and
operations for a particular OpenStack service. Examples of services include:
Compute, Object Storage, Block Storage. In order to define one, you need to
pass in the parent provider, like so:
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts)
Resource structs are the domain models that services make use of in order
to work with and represent the state of API resources:
server, err := servers.Get(client, "{serverId}").Extract()
Intermediate Result structs are returned for API operations, which allow
generic access to the HTTP headers, response body, and any errors associated
with the network transaction. To turn a result into a usable resource struct,
you must call the Extract method which is chained to the response, or an
Extract function from an applicable extension:
result := servers.Get(client, "{serverId}")
// Attempt to extract the disk configuration from the OS-DCF disk config
// extension:
config, err := diskconfig.ExtractGet(result)
All requests that enumerate a collection return a Pager struct that is used to
iterate through the results one page at a time. Use the EachPage method on that
Pager to handle each successive Page in a closure, then use the appropriate
extraction method from that request's package to interpret that Page as a slice
of results:
err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
s, err := servers.ExtractServers(page)
if err != nil {
return false, err
}
// Handle the []servers.Server slice.
// Return "false" or an error to prematurely stop fetching new pages.
return true, nil
})
This top-level package contains utility functions and data types that are used
throughout the provider and service packages. Of particular note for end users
are the AuthOptions and EndpointOpts structs.
*/
package gophercloud

View File

@ -0,0 +1,76 @@
package gophercloud
// Availability indicates to whom a specific service endpoint is accessible:
// the internet at large, internal networks only, or only to administrators.
// Different identity services use different terminology for these. Identity v2
// lists them as different kinds of URLs within the service catalog ("adminURL",
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
// endpoint's response.
type Availability string
const (
// AvailabilityAdmin indicates that an endpoint is only available to
// administrators.
AvailabilityAdmin Availability = "admin"
// AvailabilityPublic indicates that an endpoint is available to everyone on
// the internet.
AvailabilityPublic Availability = "public"
// AvailabilityInternal indicates that an endpoint is only available within
// the cluster's internal network.
AvailabilityInternal Availability = "internal"
)
// EndpointOpts specifies search criteria used by queries against an
// OpenStack service catalog. The options must contain enough information to
// unambiguously identify one, and only one, endpoint within the catalog.
//
// Usually, these are passed to service client factory functions in a provider
// package, like "rackspace.NewComputeV2()".
type EndpointOpts struct {
// Type [required] is the service type for the client (e.g., "compute",
// "object-store"). Generally, this will be supplied by the service client
// function, but a user-given value will be honored if provided.
Type string
// Name [optional] is the service name for the client (e.g., "nova") as it
// appears in the service catalog. Services can have the same Type but a
// different Name, which is why both Type and Name are sometimes needed.
Name string
// Region [required] is the geographic region in which the endpoint resides,
// generally specifying which datacenter should house your resources.
// Required only for services that span multiple regions.
Region string
// Availability [optional] is the visibility of the endpoint to be returned.
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
// or AvailabilityAdmin from this package.
//
// Availability is not required, and defaults to AvailabilityPublic. Not all
// providers or services offer all Availability options.
Availability Availability
}
/*
EndpointLocator is an internal function to be used by provider implementations.
It provides an implementation that locates a single endpoint from a service
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
provider then uses it to discover related ServiceClients.
*/
type EndpointLocator func(EndpointOpts) (string, error)
// ApplyDefaults is an internal method to be used by provider implementations.
//
// It sets EndpointOpts fields if not already set, including a default type.
// Currently, EndpointOpts.Availability defaults to the public endpoint.
func (eo *EndpointOpts) ApplyDefaults(t string) {
if eo.Type == "" {
eo.Type = t
}
if eo.Availability == "" {
eo.Availability = AvailabilityPublic
}
}

408
vendor/github.com/gophercloud/gophercloud/errors.go generated vendored Normal file
View File

@ -0,0 +1,408 @@
package gophercloud
import "fmt"
// BaseError is an error type that all other error types embed.
type BaseError struct {
DefaultErrString string
Info string
}
func (e BaseError) Error() string {
e.DefaultErrString = "An error occurred while executing a Gophercloud request."
return e.choseErrString()
}
func (e BaseError) choseErrString() string {
if e.Info != "" {
return e.Info
}
return e.DefaultErrString
}
// ErrMissingInput is the error when input is required in a particular
// situation but not provided by the user
type ErrMissingInput struct {
BaseError
Argument string
}
func (e ErrMissingInput) Error() string {
e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
return e.choseErrString()
}
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
type ErrInvalidInput struct {
ErrMissingInput
Value interface{}
}
func (e ErrInvalidInput) Error() string {
e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
return e.choseErrString()
}
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
// those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct {
BaseError
URL string
Method string
Expected []int
Actual int
Body []byte
}
func (e ErrUnexpectedResponseCode) Error() string {
e.DefaultErrString = fmt.Sprintf(
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
e.Expected, e.Method, e.URL, e.Actual, e.Body,
)
return e.choseErrString()
}
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
type ErrDefault400 struct {
ErrUnexpectedResponseCode
}
// ErrDefault401 is the default error type returned on a 401 HTTP response code.
type ErrDefault401 struct {
ErrUnexpectedResponseCode
}
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
type ErrDefault404 struct {
ErrUnexpectedResponseCode
}
// ErrDefault405 is the default error type returned on a 405 HTTP response code.
type ErrDefault405 struct {
ErrUnexpectedResponseCode
}
// ErrDefault408 is the default error type returned on a 408 HTTP response code.
type ErrDefault408 struct {
ErrUnexpectedResponseCode
}
// ErrDefault429 is the default error type returned on a 429 HTTP response code.
type ErrDefault429 struct {
ErrUnexpectedResponseCode
}
// ErrDefault500 is the default error type returned on a 500 HTTP response code.
type ErrDefault500 struct {
ErrUnexpectedResponseCode
}
// ErrDefault503 is the default error type returned on a 503 HTTP response code.
type ErrDefault503 struct {
ErrUnexpectedResponseCode
}
func (e ErrDefault400) Error() string {
return "Invalid request due to incorrect syntax or missing required parameters."
}
func (e ErrDefault401) Error() string {
return "Authentication failed"
}
func (e ErrDefault404) Error() string {
return "Resource not found"
}
func (e ErrDefault405) Error() string {
return "Method not allowed"
}
func (e ErrDefault408) Error() string {
return "The server timed out waiting for the request"
}
func (e ErrDefault429) Error() string {
return "Too many requests have been sent in a given amount of time. Pause" +
" requests, wait up to one minute, and try again."
}
func (e ErrDefault500) Error() string {
return "Internal Server Error"
}
func (e ErrDefault503) Error() string {
return "The service is currently unable to handle the request due to a temporary" +
" overloading or maintenance. This is a temporary condition. Try again later."
}
// Err400er is the interface resource error types implement to override the error message
// from a 400 error.
type Err400er interface {
Error400(ErrUnexpectedResponseCode) error
}
// Err401er is the interface resource error types implement to override the error message
// from a 401 error.
type Err401er interface {
Error401(ErrUnexpectedResponseCode) error
}
// Err404er is the interface resource error types implement to override the error message
// from a 404 error.
type Err404er interface {
Error404(ErrUnexpectedResponseCode) error
}
// Err405er is the interface resource error types implement to override the error message
// from a 405 error.
type Err405er interface {
Error405(ErrUnexpectedResponseCode) error
}
// Err408er is the interface resource error types implement to override the error message
// from a 408 error.
type Err408er interface {
Error408(ErrUnexpectedResponseCode) error
}
// Err429er is the interface resource error types implement to override the error message
// from a 429 error.
type Err429er interface {
Error429(ErrUnexpectedResponseCode) error
}
// Err500er is the interface resource error types implement to override the error message
// from a 500 error.
type Err500er interface {
Error500(ErrUnexpectedResponseCode) error
}
// Err503er is the interface resource error types implement to override the error message
// from a 503 error.
type Err503er interface {
Error503(ErrUnexpectedResponseCode) error
}
// ErrTimeOut is the error type returned when an operations times out.
type ErrTimeOut struct {
BaseError
}
func (e ErrTimeOut) Error() string {
e.DefaultErrString = "A time out occurred"
return e.choseErrString()
}
// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
type ErrUnableToReauthenticate struct {
BaseError
ErrOriginal error
}
func (e ErrUnableToReauthenticate) Error() string {
e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
return e.choseErrString()
}
// ErrErrorAfterReauthentication is the error type returned when reauthentication
// succeeds, but an error occurs afterword (usually an HTTP error).
type ErrErrorAfterReauthentication struct {
BaseError
ErrOriginal error
}
func (e ErrErrorAfterReauthentication) Error() string {
e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
return e.choseErrString()
}
// ErrServiceNotFound is returned when no service in a service catalog matches
// the provided EndpointOpts. This is generally returned by provider service
// factory methods like "NewComputeV2()" and can mean that a service is not
// enabled for your account.
type ErrServiceNotFound struct {
BaseError
}
func (e ErrServiceNotFound) Error() string {
e.DefaultErrString = "No suitable service could be found in the service catalog."
return e.choseErrString()
}
// ErrEndpointNotFound is returned when no available endpoints match the
// provided EndpointOpts. This is also generally returned by provider service
// factory methods, and usually indicates that a region was specified
// incorrectly.
type ErrEndpointNotFound struct {
BaseError
}
func (e ErrEndpointNotFound) Error() string {
e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
return e.choseErrString()
}
// ErrResourceNotFound is the error when trying to retrieve a resource's
// ID by name and the resource doesn't exist.
type ErrResourceNotFound struct {
BaseError
Name string
ResourceType string
}
func (e ErrResourceNotFound) Error() string {
e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
return e.choseErrString()
}
// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
// ID by name and multiple resources have the user-provided name.
type ErrMultipleResourcesFound struct {
BaseError
Name string
Count int
ResourceType string
}
func (e ErrMultipleResourcesFound) Error() string {
e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
return e.choseErrString()
}
// ErrUnexpectedType is the error when an unexpected type is encountered
type ErrUnexpectedType struct {
BaseError
Expected string
Actual string
}
func (e ErrUnexpectedType) Error() string {
e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
return e.choseErrString()
}
func unacceptedAttributeErr(attribute string) string {
return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
}
func redundantWithTokenErr(attribute string) string {
return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
}
func redundantWithUserID(attribute string) string {
return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
}
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
type ErrAPIKeyProvided struct{ BaseError }
func (e ErrAPIKeyProvided) Error() string {
return unacceptedAttributeErr("APIKey")
}
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
type ErrTenantIDProvided struct{ BaseError }
func (e ErrTenantIDProvided) Error() string {
return unacceptedAttributeErr("TenantID")
}
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
type ErrTenantNameProvided struct{ BaseError }
func (e ErrTenantNameProvided) Error() string {
return unacceptedAttributeErr("TenantName")
}
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
type ErrUsernameWithToken struct{ BaseError }
func (e ErrUsernameWithToken) Error() string {
return redundantWithTokenErr("Username")
}
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
type ErrUserIDWithToken struct{ BaseError }
func (e ErrUserIDWithToken) Error() string {
return redundantWithTokenErr("UserID")
}
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
type ErrDomainIDWithToken struct{ BaseError }
func (e ErrDomainIDWithToken) Error() string {
return redundantWithTokenErr("DomainID")
}
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
type ErrDomainNameWithToken struct{ BaseError }
func (e ErrDomainNameWithToken) Error() string {
return redundantWithTokenErr("DomainName")
}
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
type ErrUsernameOrUserID struct{ BaseError }
func (e ErrUsernameOrUserID) Error() string {
return "Exactly one of Username and UserID must be provided for password authentication"
}
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
type ErrDomainIDWithUserID struct{ BaseError }
func (e ErrDomainIDWithUserID) Error() string {
return redundantWithUserID("DomainID")
}
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
type ErrDomainNameWithUserID struct{ BaseError }
func (e ErrDomainNameWithUserID) Error() string {
return redundantWithUserID("DomainName")
}
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
// It may also indicate that both a DomainID and a DomainName were provided at once.
type ErrDomainIDOrDomainName struct{ BaseError }
func (e ErrDomainIDOrDomainName) Error() string {
return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
}
// ErrMissingPassword indicates that no password was provided and no token is available.
type ErrMissingPassword struct{ BaseError }
func (e ErrMissingPassword) Error() string {
return "You must provide a password to authenticate"
}
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
type ErrScopeDomainIDOrDomainName struct{ BaseError }
func (e ErrScopeDomainIDOrDomainName) Error() string {
return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
}
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
type ErrScopeProjectIDOrProjectName struct{ BaseError }
func (e ErrScopeProjectIDOrProjectName) Error() string {
return "You must provide at most one of ProjectID or ProjectName in a Scope"
}
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
type ErrScopeProjectIDAlone struct{ BaseError }
func (e ErrScopeProjectIDAlone) Error() string {
return "ProjectID must be supplied alone in a Scope"
}
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
type ErrScopeDomainName struct{ BaseError }
func (e ErrScopeDomainName) Error() string {
return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
}
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope"
}

View File

@ -0,0 +1,52 @@
package openstack
import (
"os"
"github.com/gophercloud/gophercloud"
)
var nilOptions = gophercloud.AuthOptions{}
// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
authURL := os.Getenv("OS_AUTH_URL")
username := os.Getenv("OS_USERNAME")
userID := os.Getenv("OS_USERID")
password := os.Getenv("OS_PASSWORD")
tenantID := os.Getenv("OS_TENANT_ID")
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
if authURL == "" {
err := gophercloud.ErrMissingInput{Argument: "authURL"}
return nilOptions, err
}
if username == "" && userID == "" {
err := gophercloud.ErrMissingInput{Argument: "username"}
return nilOptions, err
}
if password == "" {
err := gophercloud.ErrMissingInput{Argument: "password"}
return nilOptions, err
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
}
return ao, nil
}

View File

@ -0,0 +1,5 @@
// Package volumes provides information and interaction with volumes in the
// OpenStack Block Storage service. A volume is a detachable block storage
// device, akin to a USB hard drive. It can only be attached to one instance at
// a time.
package volumes

View File

@ -0,0 +1,167 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
Size int `json:"size" required:"true"`
Availability string `json:"availability,omitempty"`
Description string `json:"display_description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
Name string `json:"display_name,omitempty"`
SnapshotID string `json:"snapshot_id,omitempty"`
SourceVolID string `json:"source_volid,omitempty"`
ImageID string `json:"imageRef,omitempty"`
VolumeType string `json:"volume_type,omitempty"`
}
// ToVolumeCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Create will create a new Volume based on the values in CreateOpts. To extract
// the Volume object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToVolumeCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToVolumeListQuery() (string, error)
}
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
// admin-only option. Set it to true to see all tenant volumes.
AllTenants bool `q:"all_tenants"`
// List only volumes that contain Metadata.
Metadata map[string]string `q:"metadata"`
// List only volumes that have Name as the display name.
Name string `q:"display_name"`
// List only volumes that have a status of Status.
Status string `q:"status"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToVolumeListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return VolumePage{pagination.SinglePageBase(r)}
})
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"display_name,omitempty"`
Description string `json:"display_description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToVolumeUpdateMap assembles a request body based on the contents of an
// UpdateOpts.
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToVolumeUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractVolumes(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
}
}

View File

@ -0,0 +1,89 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Volume contains all the information associated with an OpenStack Volume.
type Volume struct {
// Current status of the volume.
Status string `json:"status"`
// Human-readable display name for the volume.
Name string `json:"display_name"`
// Instances onto which the volume is attached.
Attachments []map[string]interface{} `json:"attachments"`
// This parameter is no longer used.
AvailabilityZone string `json:"availability_zone"`
// Indicates whether this is a bootable volume.
Bootable string `json:"bootable"`
// The date when this volume was created.
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
// Human-readable description for the volume.
Description string `json:"display_description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `json:"volume_type"`
// The ID of the snapshot from which the volume was created
SnapshotID string `json:"snapshot_id"`
// The ID of another block storage volume from which the current volume was created
SourceVolID string `json:"source_volid"`
// Arbitrary key-value pairs defined by the user.
Metadata map[string]string `json:"metadata"`
// Unique identifier for the volume.
ID string `json:"id"`
// Size of the volume in GB.
Size int `json:"size"`
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}
// VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a VolumePage contains no Volumes.
func (r VolumePage) IsEmpty() (bool, error) {
volumes, err := ExtractVolumes(r)
return len(volumes) == 0, err
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s struct {
Volumes []Volume `json:"volumes"`
}
err := (r.(VolumePage)).ExtractInto(&s)
return s.Volumes, err
}
// UpdateResult contains the response body and error from an Update request.
type UpdateResult struct {
commonResult
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) {
var s struct {
Volume *Volume `json:"volume"`
}
err := r.ExtractInto(&s)
return s.Volume, err
}

View File

@ -0,0 +1,23 @@
package volumes
import "github.com/gophercloud/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("volumes")
}
func listURL(c *gophercloud.ServiceClient) string {
return createURL(c)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("volumes", id)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}

View File

@ -0,0 +1,22 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,5 @@
// Package volumes provides information and interaction with volumes in the
// OpenStack Block Storage service. A volume is a detachable block storage
// device, akin to a USB hard drive. It can only be attached to one instance at
// a time.
package volumes

View File

@ -0,0 +1,182 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToVolumeCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains options for creating a Volume. This object is passed to
// the volumes.Create function. For more information about these parameters,
// see the Volume object.
type CreateOpts struct {
// The size of the volume, in GB
Size int `json:"size" required:"true"`
// The availability zone
AvailabilityZone string `json:"availability_zone,omitempty"`
// ConsistencyGroupID is the ID of a consistency group
ConsistencyGroupID string `json:"consistencygroup_id,omitempty"`
// The volume description
Description string `json:"description,omitempty"`
// One or more metadata key and value pairs to associate with the volume
Metadata map[string]string `json:"metadata,omitempty"`
// The volume name
Name string `json:"name,omitempty"`
// the ID of the existing volume snapshot
SnapshotID string `json:"snapshot_id,omitempty"`
// SourceReplica is a UUID of an existing volume to replicate with
SourceReplica string `json:"source_replica,omitempty"`
// the ID of the existing volume
SourceVolID string `json:"source_volid,omitempty"`
// The ID of the image from which you want to create the volume.
// Required to create a bootable volume.
ImageID string `json:"imageRef,omitempty"`
// The associated volume type
VolumeType string `json:"volume_type,omitempty"`
}
// ToVolumeCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Create will create a new Volume based on the values in CreateOpts. To extract
// the Volume object from the response, call the Extract method on the
// CreateResult.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToVolumeCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
return
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListOptsBuilder interface {
ToVolumeListQuery() (string, error)
}
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
// function.
type ListOpts struct {
// admin-only option. Set it to true to see all tenant volumes.
AllTenants bool `q:"all_tenants"`
// List only volumes that contain Metadata.
Metadata map[string]string `q:"metadata"`
// List only volumes that have Name as the display name.
Name string `q:"name"`
// List only volumes that have a status of Status.
Status string `q:"status"`
}
// ToVolumeListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToVolumeListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns Volumes optionally limited by the conditions provided in ListOpts.
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
query, err := opts.ToVolumeListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return VolumePage{pagination.SinglePageBase(r)}
})
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToVolumeUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contain options for updating an existing Volume. This object is passed
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToVolumeUpdateMap assembles a request body based on the contents of an
// UpdateOpts.
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "volume")
}
// Update will update the Volume with provided information. To extract the updated
// Volume from the response, call the Extract method on the UpdateResult.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToVolumeUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a server's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractVolumes(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
}
}

View File

@ -0,0 +1,121 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
type Attachment struct {
AttachedAt gophercloud.JSONRFC3339MilliNoZ `json:"attached_at"`
AttachmentID string `json:"attachment_id"`
Device string `json:"device"`
HostName string `json:"host_name"`
ID string `json:"id"`
ServerID string `json:"server_id"`
VolumeID string `json:"volume_id"`
}
// Volume contains all the information associated with an OpenStack Volume.
type Volume struct {
// Unique identifier for the volume.
ID string `json:"id"`
// Current status of the volume.
Status string `json:"status"`
// Size of the volume in GB.
Size int `json:"size"`
// AvailabilityZone is which availability zone the volume is in.
AvailabilityZone string `json:"availability_zone"`
// The date when this volume was created.
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
// The date when this volume was last updated
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
// Instances onto which the volume is attached.
Attachments []Attachment `json:"attachments"`
// Human-readable display name for the volume.
Name string `json:"name"`
// Human-readable description for the volume.
Description string `json:"description"`
// The type of volume to create, either SATA or SSD.
VolumeType string `json:"volume_type"`
// The ID of the snapshot from which the volume was created
SnapshotID string `json:"snapshot_id"`
// The ID of another block storage volume from which the current volume was created
SourceVolID string `json:"source_volid"`
// Arbitrary key-value pairs defined by the user.
Metadata map[string]string `json:"metadata"`
// UserID is the id of the user who created the volume.
UserID string `json:"user_id"`
// Indicates whether this is a bootable volume.
Bootable string `json:"bootable"`
// Encrypted denotes if the volume is encrypted.
Encrypted bool `json:"encrypted"`
// ReplicationStatus is the status of replication.
ReplicationStatus string `json:"replication_status"`
// ConsistencyGroupID is the consistency group ID.
ConsistencyGroupID string `json:"consistencygroup_id"`
// Multiattach denotes if the volume is multi-attach capable.
Multiattach bool `json:"multiattach"`
}
/*
THESE BELONG IN EXTENSIONS:
// ReplicationDriverData contains data about the replication driver.
ReplicationDriverData string `json:"os-volume-replication:driver_data"`
// ReplicationExtendedStatus contains extended status about replication.
ReplicationExtendedStatus string `json:"os-volume-replication:extended_status"`
// TenantID is the id of the project that owns the volume.
TenantID string `json:"os-vol-tenant-attr:tenant_id"`
*/
// VolumePage is a pagination.pager that is returned from a call to the List function.
type VolumePage struct {
pagination.SinglePageBase
}
// IsEmpty returns true if a ListResult contains no Volumes.
func (r VolumePage) IsEmpty() (bool, error) {
volumes, err := ExtractVolumes(r)
return len(volumes) == 0, err
}
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
func ExtractVolumes(r pagination.Page) ([]Volume, error) {
var s struct {
Volumes []Volume `json:"volumes"`
}
err := (r.(VolumePage)).ExtractInto(&s)
return s.Volumes, err
}
type commonResult struct {
gophercloud.Result
}
// Extract will get the Volume object out of the commonResult object.
func (r commonResult) Extract() (*Volume, error) {
var s struct {
Volume *Volume `json:"volume"`
}
err := r.ExtractInto(&s)
return s.Volume, err
}
// CreateResult contains the response body and error from a Create request.
type CreateResult struct {
commonResult
}
// GetResult contains the response body and error from a Get request.
type GetResult struct {
commonResult
}
// UpdateResult contains the response body and error from an Update request.
type UpdateResult struct {
commonResult
}
// DeleteResult contains the response body and error from a Delete request.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,23 @@
package volumes
import "github.com/gophercloud/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("volumes")
}
func listURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("volumes", "detail")
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("volumes", id)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return deleteURL(c, id)
}

View File

@ -0,0 +1,22 @@
package volumes
import (
"github.com/gophercloud/gophercloud"
)
// WaitForStatus will continually poll the resource, checking for a particular
// status. It will do this for the amount of seconds defined.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()
if err != nil {
return false, err
}
if current.Status == status {
return true, nil
}
return false, nil
})
}

View File

@ -0,0 +1,311 @@
package openstack
import (
"fmt"
"net/url"
"reflect"
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"github.com/gophercloud/gophercloud/openstack/utils"
)
const (
v20 = "v2.0"
v30 = "v3.0"
)
// NewClient prepares an unauthenticated ProviderClient instance.
// Most users will probably prefer using the AuthenticatedClient function instead.
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
// for example.
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
hadPath := u.Path != ""
u.Path, u.RawQuery, u.Fragment = "", "", ""
base := u.String()
endpoint = gophercloud.NormalizeURL(endpoint)
base = gophercloud.NormalizeURL(base)
if hadPath {
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: endpoint,
}, nil
}
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: "",
}, nil
}
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
// returns a Client instance that's ready to operate.
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
// the most recent identity service available to proceed.
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
client, err := NewClient(options.IdentityEndpoint)
if err != nil {
return nil, err
}
err = Authenticate(client, options)
if err != nil {
return nil, err
}
return client, nil
}
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
{ID: v30, Priority: 30, Suffix: "/v3/"},
}
chosen, endpoint, err := utils.ChooseVersion(client, versions)
if err != nil {
return err
}
switch chosen.ID {
case v20:
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
case v30:
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
default:
// The switch statement must be out of date from the versions list.
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
}
}
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
return v2auth(client, "", options, eo)
}
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
v2Client, err := NewIdentityV2(client, eo)
if err != nil {
return err
}
if endpoint != "" {
v2Client.Endpoint = endpoint
}
v2Opts := tokens2.AuthOptions{
IdentityEndpoint: options.IdentityEndpoint,
Username: options.Username,
Password: options.Password,
TenantID: options.TenantID,
TenantName: options.TenantName,
AllowReauth: options.AllowReauth,
TokenID: options.TokenID,
}
result := tokens2.Create(v2Client, v2Opts)
token, err := result.ExtractToken()
if err != nil {
return err
}
catalog, err := result.ExtractServiceCatalog()
if err != nil {
return err
}
if options.AllowReauth {
client.ReauthFunc = func() error {
client.TokenID = ""
return v2auth(client, endpoint, options, eo)
}
}
client.TokenID = token.ID
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V2EndpointURL(catalog, opts)
}
return nil
}
// AuthenticateV3 explicitly authenticates against the identity v3 service.
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
return v3auth(client, "", options, eo)
}
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
// Override the generated service endpoint with the one returned by the version endpoint.
v3Client, err := NewIdentityV3(client, eo)
if err != nil {
return err
}
if endpoint != "" {
v3Client.Endpoint = endpoint
}
result := tokens3.Create(v3Client, opts)
token, err := result.ExtractToken()
if err != nil {
return err
}
catalog, err := result.ExtractServiceCatalog()
if err != nil {
return err
}
client.TokenID = token.ID
if opts.CanReauth() {
client.ReauthFunc = func() error {
client.TokenID = ""
return v3auth(client, endpoint, opts, eo)
}
}
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V3EndpointURL(catalog, opts)
}
return nil
}
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v2.0/"
var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity")
endpoint, err = client.EndpointLocator(eo)
if err != nil {
return nil, err
}
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
}, nil
}
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v3/"
var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity")
endpoint, err = client.EndpointLocator(eo)
if err != nil {
return nil, err
}
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
}, nil
}
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("object-store")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("compute")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("network")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2.0/",
}, nil
}
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volume")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service.
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volumev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("sharev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("cdn")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("orchestration")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("database")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

View File

@ -0,0 +1,120 @@
package bootfromvolume
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
type (
// DestinationType represents the type of medium being used as the
// destination of the bootable device.
DestinationType string
// SourceType represents the type of medium being used as the source of the
// bootable device.
SourceType string
)
const (
// DestinationLocal DestinationType is for using an ephemeral disk as the
// destination.
DestinationLocal DestinationType = "local"
// DestinationVolume DestinationType is for using a volume as the destination.
DestinationVolume DestinationType = "volume"
// SourceBlank SourceType is for a "blank" or empty source.
SourceBlank SourceType = "blank"
// SourceImage SourceType is for using images as the source of a block device.
SourceImage SourceType = "image"
// SourceSnapshot SourceType is for using a volume snapshot as the source of
// a block device.
SourceSnapshot SourceType = "snapshot"
// SourceVolume SourceType is for using a volume as the source of block
// device.
SourceVolume SourceType = "volume"
)
// BlockDevice is a structure with options for creating block devices in a
// server. The block device may be created from an image, snapshot, new volume,
// or existing volume. The destination may be a new volume, existing volume
// which will be attached to the instance, ephemeral disk, or boot device.
type BlockDevice struct {
// SourceType must be one of: "volume", "snapshot", "image", or "blank".
SourceType SourceType `json:"source_type" required:"true"`
// UUID is the unique identifier for the existing volume, snapshot, or
// image (see above).
UUID string `json:"uuid,omitempty"`
// BootIndex is the boot index. It defaults to 0.
BootIndex int `json:"boot_index"`
// DeleteOnTermination specifies whether or not to delete the attached volume
// when the server is deleted. Defaults to `false`.
DeleteOnTermination bool `json:"delete_on_termination"`
// DestinationType is the type that gets created. Possible values are "volume"
// and "local".
DestinationType DestinationType `json:"destination_type,omitempty"`
// GuestFormat specifies the format of the block device.
GuestFormat string `json:"guest_format,omitempty"`
// VolumeSize is the size of the volume to create (in gigabytes). This can be
// omitted for existing volumes.
VolumeSize int `json:"volume_size,omitempty"`
}
// CreateOptsExt is a structure that extends the server `CreateOpts` structure
// by allowing for a block device mapping.
type CreateOptsExt struct {
servers.CreateOptsBuilder
BlockDevice []BlockDevice `json:"block_device_mapping_v2,omitempty"`
}
// ToServerCreateMap adds the block device mapping option to the base server
// creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
return nil, err
}
if len(opts.BlockDevice) == 0 {
err := gophercloud.ErrMissingInput{}
err.Argument = "bootfromvolume.CreateOptsExt.BlockDevice"
return nil, err
}
serverMap := base["server"].(map[string]interface{})
blockDevice := make([]map[string]interface{}, len(opts.BlockDevice))
for i, bd := range opts.BlockDevice {
b, err := gophercloud.BuildRequestBody(bd, "")
if err != nil {
return nil, err
}
blockDevice[i] = b
}
serverMap["block_device_mapping_v2"] = blockDevice
return base, nil
}
// Create requests the creation of a server from the given block device mapping.
func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) (r servers.CreateResult) {
b, err := opts.ToServerCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202},
})
return
}

View File

@ -0,0 +1,10 @@
package bootfromvolume
import (
os "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
// CreateResult temporarily contains the response from a Create call.
type CreateResult struct {
os.CreateResult
}

View File

@ -0,0 +1,7 @@
package bootfromvolume
import "github.com/gophercloud/gophercloud"
func createURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-volumes_boot")
}

View File

@ -0,0 +1,3 @@
// Package floatingips provides the ability to manage floating ips through
// nova-network
package floatingips

View File

@ -0,0 +1,112 @@
package floatingips
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// List returns a Pager that allows you to iterate over a collection of FloatingIPs.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
return FloatingIPPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
// CreateOpts struct in this package does.
type CreateOptsBuilder interface {
ToFloatingIPCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies a Floating IP allocation request
type CreateOpts struct {
// Pool is the pool of floating IPs to allocate one from
Pool string `json:"pool" required:"true"`
}
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// Create requests the creation of a new floating IP
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToFloatingIPCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Get returns data about a previously created FloatingIP.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// Delete requests the deletion of a previous allocated FloatingIP.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// AssociateOptsBuilder is the interface types must satfisfy to be used as
// Associate options
type AssociateOptsBuilder interface {
ToFloatingIPAssociateMap() (map[string]interface{}, error)
}
// AssociateOpts specifies the required information to associate a floating IP with an instance
type AssociateOpts struct {
// FloatingIP is the floating IP to associate with an instance
FloatingIP string `json:"address" required:"true"`
// FixedIP is an optional fixed IP address of the server
FixedIP string `json:"fixed_address,omitempty"`
}
// ToFloatingIPAssociateMap constructs a request body from AssociateOpts.
func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "addFloatingIp")
}
// AssociateInstance pairs an allocated floating IP with an instance.
func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) {
b, err := opts.ToFloatingIPAssociateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(associateURL(client, serverID), b, nil, nil)
return
}
// DisassociateOptsBuilder is the interface types must satfisfy to be used as
// Disassociate options
type DisassociateOptsBuilder interface {
ToFloatingIPDisassociateMap() (map[string]interface{}, error)
}
// DisassociateOpts specifies the required information to disassociate a floating IP with an instance
type DisassociateOpts struct {
FloatingIP string `json:"address" required:"true"`
}
// ToFloatingIPDisassociateMap constructs a request body from AssociateOpts.
func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "removeFloatingIp")
}
// DisassociateInstance decouples an allocated floating IP from an instance
func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, opts DisassociateOptsBuilder) (r DisassociateResult) {
b, err := opts.ToFloatingIPDisassociateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil)
return
}

View File

@ -0,0 +1,91 @@
package floatingips
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// A FloatingIP is an IP that can be associated with an instance
type FloatingIP struct {
// ID is a unique ID of the Floating IP
ID string `json:"id"`
// FixedIP is the IP of the instance related to the Floating IP
FixedIP string `json:"fixed_ip,omitempty"`
// InstanceID is the ID of the instance that is using the Floating IP
InstanceID string `json:"instance_id"`
// IP is the actual Floating IP
IP string `json:"ip"`
// Pool is the pool of floating IPs that this floating IP belongs to
Pool string `json:"pool"`
}
// FloatingIPPage stores a single, only page of FloatingIPs
// results from a List call.
type FloatingIPPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a FloatingIPsPage is empty.
func (page FloatingIPPage) IsEmpty() (bool, error) {
va, err := ExtractFloatingIPs(page)
return len(va) == 0, err
}
// ExtractFloatingIPs interprets a page of results as a slice of
// FloatingIPs.
func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
var s struct {
FloatingIPs []FloatingIP `json:"floating_ips"`
}
err := (r.(FloatingIPPage)).ExtractInto(&s)
return s.FloatingIPs, err
}
// FloatingIPResult is the raw result from a FloatingIP request.
type FloatingIPResult struct {
gophercloud.Result
}
// Extract is a method that attempts to interpret any FloatingIP resource
// response as a FloatingIP struct.
func (r FloatingIPResult) Extract() (*FloatingIP, error) {
var s struct {
FloatingIP *FloatingIP `json:"floating_ip"`
}
err := r.ExtractInto(&s)
return s.FloatingIP, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a FloatingIP.
type CreateResult struct {
FloatingIPResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a FloatingIP.
type GetResult struct {
FloatingIPResult
}
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// AssociateResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
type AssociateResult struct {
gophercloud.ErrResult
}
// DisassociateResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
type DisassociateResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,37 @@
package floatingips
import "github.com/gophercloud/gophercloud"
const resourcePath = "os-floating-ips"
func resourceURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(resourcePath)
}
func listURL(c *gophercloud.ServiceClient) string {
return resourceURL(c)
}
func createURL(c *gophercloud.ServiceClient) string {
return resourceURL(c)
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(resourcePath, id)
}
func deleteURL(c *gophercloud.ServiceClient, id string) string {
return getURL(c, id)
}
func serverURL(c *gophercloud.ServiceClient, serverID string) string {
return c.ServiceURL("servers/" + serverID + "/action")
}
func associateURL(c *gophercloud.ServiceClient, serverID string) string {
return serverURL(c, serverID)
}
func disassociateURL(c *gophercloud.ServiceClient, serverID string) string {
return serverURL(c, serverID)
}

View File

@ -0,0 +1,3 @@
// Package keypairs provides information and interaction with the Keypairs
// extension for the OpenStack Compute service.
package keypairs

View File

@ -0,0 +1,84 @@
package keypairs
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsExt adds a KeyPair option to the base CreateOpts.
type CreateOptsExt struct {
servers.CreateOptsBuilder
KeyName string `json:"key_name,omitempty"`
}
// ToServerCreateMap adds the key_name and, optionally, key_data options to
// the base server creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
return nil, err
}
if opts.KeyName == "" {
return base, nil
}
serverMap := base["server"].(map[string]interface{})
serverMap["key_name"] = opts.KeyName
return base, nil
}
// List returns a Pager that allows you to iterate over a collection of KeyPairs.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
return KeyPairPage{pagination.SinglePageBase(r)}
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
// CreateOpts struct in this package does.
type CreateOptsBuilder interface {
ToKeyPairCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies keypair creation or import parameters.
type CreateOpts struct {
// Name is a friendly name to refer to this KeyPair in other services.
Name string `json:"name" required:"true"`
// PublicKey [optional] is a pregenerated OpenSSH-formatted public key. If provided, this key
// will be imported and no new key will be created.
PublicKey string `json:"public_key,omitempty"`
}
// ToKeyPairCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "keypair")
}
// Create requests the creation of a new keypair on the server, or to import a pre-existing
// keypair.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToKeyPairCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Get returns public data about a previously uploaded KeyPair.
func Get(client *gophercloud.ServiceClient, name string) (r GetResult) {
_, r.Err = client.Get(getURL(client, name), &r.Body, nil)
return
}
// Delete requests the deletion of a previous stored KeyPair from the server.
func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, name), nil)
return
}

View File

@ -0,0 +1,86 @@
package keypairs
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// KeyPair is an SSH key known to the OpenStack cluster that is available to be injected into
// servers.
type KeyPair struct {
// Name is used to refer to this keypair from other services within this region.
Name string `json:"name"`
// Fingerprint is a short sequence of bytes that can be used to authenticate or validate a longer
// public key.
Fingerprint string `json:"fingerprint"`
// PublicKey is the public key from this pair, in OpenSSH format. "ssh-rsa AAAAB3Nz..."
PublicKey string `json:"public_key"`
// PrivateKey is the private key from this pair, in PEM format.
// "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..." It is only present if this keypair was just
// returned from a Create call
PrivateKey string `json:"private_key"`
// UserID is the user who owns this keypair.
UserID string `json:"user_id"`
}
// KeyPairPage stores a single, only page of KeyPair results from a List call.
type KeyPairPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a KeyPairPage is empty.
func (page KeyPairPage) IsEmpty() (bool, error) {
ks, err := ExtractKeyPairs(page)
return len(ks) == 0, err
}
// ExtractKeyPairs interprets a page of results as a slice of KeyPairs.
func ExtractKeyPairs(r pagination.Page) ([]KeyPair, error) {
type pair struct {
KeyPair KeyPair `json:"keypair"`
}
var s struct {
KeyPairs []pair `json:"keypairs"`
}
err := (r.(KeyPairPage)).ExtractInto(&s)
results := make([]KeyPair, len(s.KeyPairs))
for i, pair := range s.KeyPairs {
results[i] = pair.KeyPair
}
return results, err
}
type keyPairResult struct {
gophercloud.Result
}
// Extract is a method that attempts to interpret any KeyPair resource response as a KeyPair struct.
func (r keyPairResult) Extract() (*KeyPair, error) {
var s struct {
KeyPair *KeyPair `json:"keypair"`
}
err := r.ExtractInto(&s)
return s.KeyPair, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a KeyPair.
type CreateResult struct {
keyPairResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a KeyPair.
type GetResult struct {
keyPairResult
}
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,25 @@
package keypairs
import "github.com/gophercloud/gophercloud"
const resourcePath = "os-keypairs"
func resourceURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(resourcePath)
}
func listURL(c *gophercloud.ServiceClient) string {
return resourceURL(c)
}
func createURL(c *gophercloud.ServiceClient) string {
return resourceURL(c)
}
func getURL(c *gophercloud.ServiceClient, name string) string {
return c.ServiceURL(resourcePath, name)
}
func deleteURL(c *gophercloud.ServiceClient, name string) string {
return getURL(c, name)
}

View File

@ -0,0 +1,3 @@
// Package schedulerhints enables instances to provide the OpenStack scheduler
// hints about where they should be placed in the cloud.
package schedulerhints

View File

@ -0,0 +1,148 @@
package schedulerhints
import (
"net"
"regexp"
"strings"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
)
// SchedulerHints represents a set of scheduling hints that are passed to the
// OpenStack scheduler
type SchedulerHints struct {
// Group specifies a Server Group to place the instance in.
Group string
// DifferentHost will place the instance on a compute node that does not
// host the given instances.
DifferentHost []string
// SameHost will place the instance on a compute node that hosts the given
// instances.
SameHost []string
// Query is a conditional statement that results in compute nodes able to
// host the instance.
Query []interface{}
// TargetCell specifies a cell name where the instance will be placed.
TargetCell string `json:"target_cell,omitempty"`
// BuildNearHostIP specifies a subnet of compute nodes to host the instance.
BuildNearHostIP string
}
// CreateOptsBuilder builds the scheduler hints into a serializable format.
type CreateOptsBuilder interface {
ToServerSchedulerHintsCreateMap() (map[string]interface{}, error)
}
// ToServerSchedulerHintsMap builds the scheduler hints into a serializable format.
func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interface{}, error) {
sh := make(map[string]interface{})
uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$")
if opts.Group != "" {
if !uuidRegex.MatchString(opts.Group) {
err := gophercloud.ErrInvalidInput{}
err.Argument = "schedulerhints.SchedulerHints.Group"
err.Value = opts.Group
err.Info = "Group must be a UUID"
return nil, err
}
sh["group"] = opts.Group
}
if len(opts.DifferentHost) > 0 {
for _, diffHost := range opts.DifferentHost {
if !uuidRegex.MatchString(diffHost) {
err := gophercloud.ErrInvalidInput{}
err.Argument = "schedulerhints.SchedulerHints.DifferentHost"
err.Value = opts.DifferentHost
err.Info = "The hosts must be in UUID format."
return nil, err
}
}
sh["different_host"] = opts.DifferentHost
}
if len(opts.SameHost) > 0 {
for _, sameHost := range opts.SameHost {
if !uuidRegex.MatchString(sameHost) {
err := gophercloud.ErrInvalidInput{}
err.Argument = "schedulerhints.SchedulerHints.SameHost"
err.Value = opts.SameHost
err.Info = "The hosts must be in UUID format."
return nil, err
}
}
sh["same_host"] = opts.SameHost
}
/* Query can be something simple like:
[">=", "$free_ram_mb", 1024]
Or more complex like:
['and',
['>=', '$free_ram_mb', 1024],
['>=', '$free_disk_mb', 200 * 1024]
]
Because of the possible complexity, just make sure the length is a minimum of 3.
*/
if len(opts.Query) > 0 {
if len(opts.Query) < 3 {
err := gophercloud.ErrInvalidInput{}
err.Argument = "schedulerhints.SchedulerHints.Query"
err.Value = opts.Query
err.Info = "Must be a conditional statement in the format of [op,variable,value]"
return nil, err
}
sh["query"] = opts.Query
}
if opts.TargetCell != "" {
sh["target_cell"] = opts.TargetCell
}
if opts.BuildNearHostIP != "" {
if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil {
err := gophercloud.ErrInvalidInput{}
err.Argument = "schedulerhints.SchedulerHints.BuildNearHostIP"
err.Value = opts.BuildNearHostIP
err.Info = "Must be a valid subnet in the form 192.168.1.1/24"
return nil, err
}
ipParts := strings.Split(opts.BuildNearHostIP, "/")
sh["build_near_host_ip"] = ipParts[0]
sh["cidr"] = "/" + ipParts[1]
}
return sh, nil
}
// CreateOptsExt adds a SchedulerHints option to the base CreateOpts.
type CreateOptsExt struct {
servers.CreateOptsBuilder
// SchedulerHints provides a set of hints to the scheduler.
SchedulerHints CreateOptsBuilder
}
// ToServerCreateMap adds the SchedulerHints option to the base server creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
return nil, err
}
schedulerHints, err := opts.SchedulerHints.ToServerSchedulerHintsCreateMap()
if err != nil {
return nil, err
}
if len(schedulerHints) == 0 {
return base, nil
}
base["os:scheduler_hints"] = schedulerHints
return base, nil
}

View File

@ -0,0 +1 @@
package secgroups

View File

@ -0,0 +1,171 @@
package secgroups
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
func commonList(client *gophercloud.ServiceClient, url string) pagination.Pager {
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return SecurityGroupPage{pagination.SinglePageBase(r)}
})
}
// List will return a collection of all the security groups for a particular
// tenant.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return commonList(client, rootURL(client))
}
// ListByServer will return a collection of all the security groups which are
// associated with a particular server.
func ListByServer(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
return commonList(client, listByServerURL(client, serverID))
}
// GroupOpts is the underlying struct responsible for creating or updating
// security groups. It therefore represents the mutable attributes of a
// security group.
type GroupOpts struct {
// the name of your security group.
Name string `json:"name" required:"true"`
// the description of your security group.
Description string `json:"description" required:"true"`
}
// CreateOpts is the struct responsible for creating a security group.
type CreateOpts GroupOpts
// CreateOptsBuilder builds the create options into a serializable format.
type CreateOptsBuilder interface {
ToSecGroupCreateMap() (map[string]interface{}, error)
}
// ToSecGroupCreateMap builds the create options into a serializable format.
func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
// Create will create a new security group.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSecGroupCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(rootURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// UpdateOpts is the struct responsible for updating an existing security group.
type UpdateOpts GroupOpts
// UpdateOptsBuilder builds the update options into a serializable format.
type UpdateOptsBuilder interface {
ToSecGroupUpdateMap() (map[string]interface{}, error)
}
// ToSecGroupUpdateMap builds the update options into a serializable format.
func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
// Update will modify the mutable properties of a security group, notably its
// name and description.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToSecGroupUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(resourceURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Get will return details for a particular security group.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(resourceURL(client, id), &r.Body, nil)
return
}
// Delete will permanently delete a security group from the project.
func Delete(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
_, r.Err = client.Delete(resourceURL(client, id), nil)
return
}
// CreateRuleOpts represents the configuration for adding a new rule to an
// existing security group.
type CreateRuleOpts struct {
// the ID of the group that this rule will be added to.
ParentGroupID string `json:"parent_group_id" required:"true"`
// the lower bound of the port range that will be opened.
FromPort int `json:"from_port"`
// the upper bound of the port range that will be opened.
ToPort int `json:"to_port"`
// the protocol type that will be allowed, e.g. TCP.
IPProtocol string `json:"ip_protocol" required:"true"`
// ONLY required if FromGroupID is blank. This represents the IP range that
// will be the source of network traffic to your security group. Use
// 0.0.0.0/0 to allow all IP addresses.
CIDR string `json:"cidr,omitempty" or:"FromGroupID"`
// ONLY required if CIDR is blank. This value represents the ID of a group
// that forwards traffic to the parent group. So, instead of accepting
// network traffic from an entire IP range, you can instead refine the
// inbound source by an existing security group.
FromGroupID string `json:"group_id,omitempty" or:"CIDR"`
}
// CreateRuleOptsBuilder builds the create rule options into a serializable format.
type CreateRuleOptsBuilder interface {
ToRuleCreateMap() (map[string]interface{}, error)
}
// ToRuleCreateMap builds the create rule options into a serializable format.
func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group_rule")
}
// CreateRule will add a new rule to an existing security group (whose ID is
// specified in CreateRuleOpts). You have the option of controlling inbound
// traffic from either an IP range (CIDR) or from another security group.
func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) (r CreateRuleResult) {
b, err := opts.ToRuleCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(rootRuleURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// DeleteRule will permanently delete a rule from a security group.
func DeleteRule(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
_, r.Err = client.Delete(resourceRuleURL(client, id), nil)
return
}
func actionMap(prefix, groupName string) map[string]map[string]string {
return map[string]map[string]string{
prefix + "SecurityGroup": map[string]string{"name": groupName},
}
}
// AddServer will associate a server and a security group, enforcing the
// rules of the group on the server.
func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) {
_, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &r.Body, nil)
return
}
// RemoveServer will disassociate a server from a security group.
func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) (r gophercloud.ErrResult) {
_, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &r.Body, nil)
return
}

View File

@ -0,0 +1,127 @@
package secgroups
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// SecurityGroup represents a security group.
type SecurityGroup struct {
// The unique ID of the group. If Neutron is installed, this ID will be
// represented as a string UUID; if Neutron is not installed, it will be a
// numeric ID. For the sake of consistency, we always cast it to a string.
ID string
// The human-readable name of the group, which needs to be unique.
Name string
// The human-readable description of the group.
Description string
// The rules which determine how this security group operates.
Rules []Rule
// The ID of the tenant to which this security group belongs.
TenantID string `json:"tenant_id"`
}
// Rule represents a security group rule, a policy which determines how a
// security group operates and what inbound traffic it allows in.
type Rule struct {
// The unique ID. If Neutron is installed, this ID will be
// represented as a string UUID; if Neutron is not installed, it will be a
// numeric ID. For the sake of consistency, we always cast it to a string.
ID string
// The lower bound of the port range which this security group should open up
FromPort int `json:"from_port"`
// The upper bound of the port range which this security group should open up
ToPort int `json:"to_port"`
// The IP protocol (e.g. TCP) which the security group accepts
IPProtocol string `json:"ip_protocol"`
// The CIDR IP range whose traffic can be received
IPRange IPRange `json:"ip_range"`
// The security group ID to which this rule belongs
ParentGroupID string `json:"parent_group_id"`
// Not documented.
Group Group
}
// IPRange represents the IP range whose traffic will be accepted by the
// security group.
type IPRange struct {
CIDR string
}
// Group represents a group.
type Group struct {
TenantID string `json:"tenant_id"`
Name string
}
// SecurityGroupPage is a single page of a SecurityGroup collection.
type SecurityGroupPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a page of Security Groups contains any results.
func (page SecurityGroupPage) IsEmpty() (bool, error) {
users, err := ExtractSecurityGroups(page)
return len(users) == 0, err
}
// ExtractSecurityGroups returns a slice of SecurityGroups contained in a single page of results.
func ExtractSecurityGroups(r pagination.Page) ([]SecurityGroup, error) {
var s struct {
SecurityGroups []SecurityGroup `json:"security_groups"`
}
err := (r.(SecurityGroupPage)).ExtractInto(&s)
return s.SecurityGroups, err
}
type commonResult struct {
gophercloud.Result
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
commonResult
}
// Extract will extract a SecurityGroup struct from most responses.
func (r commonResult) Extract() (*SecurityGroup, error) {
var s struct {
SecurityGroup *SecurityGroup `json:"security_group"`
}
err := r.ExtractInto(&s)
return s.SecurityGroup, err
}
// CreateRuleResult represents the result when adding rules to a security group.
type CreateRuleResult struct {
gophercloud.Result
}
// Extract will extract a Rule struct from a CreateRuleResult.
func (r CreateRuleResult) Extract() (*Rule, error) {
var s struct {
Rule *Rule `json:"security_group_rule"`
}
err := r.ExtractInto(&s)
return s.Rule, err
}

Some files were not shown because too many files have changed in this diff Show More