Merge pull request #924 from jrperritt/openstack-gophercloud-v1.0
OpenStack Provider
This commit is contained in:
commit
08814a51ba
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/builtin/providers/openstack"
|
||||||
|
"github.com/hashicorp/terraform/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
|
ProviderFunc: openstack.Provider,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Username string
|
||||||
|
UserID string
|
||||||
|
Password string
|
||||||
|
APIKey string
|
||||||
|
IdentityEndpoint string
|
||||||
|
TenantID string
|
||||||
|
TenantName string
|
||||||
|
DomainID string
|
||||||
|
DomainName string
|
||||||
|
|
||||||
|
osClient *gophercloud.ProviderClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) loadAndValidate() error {
|
||||||
|
ao := gophercloud.AuthOptions{
|
||||||
|
Username: c.Username,
|
||||||
|
UserID: c.UserID,
|
||||||
|
Password: c.Password,
|
||||||
|
APIKey: c.APIKey,
|
||||||
|
IdentityEndpoint: c.IdentityEndpoint,
|
||||||
|
TenantID: c.TenantID,
|
||||||
|
TenantName: c.TenantName,
|
||||||
|
DomainID: c.DomainID,
|
||||||
|
DomainName: c.DomainName,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := openstack.AuthenticatedClient(ao)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.osClient = client
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) blockStorageV1Client(region string) (*gophercloud.ServiceClient, error) {
|
||||||
|
return openstack.NewBlockStorageV1(c.osClient, gophercloud.EndpointOpts{
|
||||||
|
Region: region,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) computeV2Client(region string) (*gophercloud.ServiceClient, error) {
|
||||||
|
return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{
|
||||||
|
Region: region,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) networkingV2Client(region string) (*gophercloud.ServiceClient, error) {
|
||||||
|
return openstack.NewNetworkV2(c.osClient, gophercloud.EndpointOpts{
|
||||||
|
Region: region,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) objectStorageV1Client(region string) (*gophercloud.ServiceClient, error) {
|
||||||
|
return openstack.NewObjectStorageV1(c.osClient, gophercloud.EndpointOpts{
|
||||||
|
Region: region,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider returns a schema.Provider for OpenStack.
|
||||||
|
func Provider() terraform.ResourceProvider {
|
||||||
|
return &schema.Provider{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"auth_url": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_AUTH_URL"),
|
||||||
|
},
|
||||||
|
"user_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_USERNAME"),
|
||||||
|
},
|
||||||
|
"user_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
"tenant_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_TENANT_NAME"),
|
||||||
|
},
|
||||||
|
"password": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_PASSWORD"),
|
||||||
|
},
|
||||||
|
"api_key": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
"domain_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
"domain_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(),
|
||||||
|
"openstack_compute_instance_v2": resourceComputeInstanceV2(),
|
||||||
|
"openstack_compute_keypair_v2": resourceComputeKeypairV2(),
|
||||||
|
"openstack_compute_secgroup_v2": resourceComputeSecGroupV2(),
|
||||||
|
"openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(),
|
||||||
|
"openstack_fw_firewall_v1": resourceFWFirewallV1(),
|
||||||
|
"openstack_fw_policy_v1": resourceFWPolicyV1(),
|
||||||
|
"openstack_fw_rule_v1": resourceFWRuleV1(),
|
||||||
|
"openstack_lb_monitor_v1": resourceLBMonitorV1(),
|
||||||
|
"openstack_lb_pool_v1": resourceLBPoolV1(),
|
||||||
|
"openstack_lb_vip_v1": resourceLBVipV1(),
|
||||||
|
"openstack_networking_network_v2": resourceNetworkingNetworkV2(),
|
||||||
|
"openstack_networking_subnet_v2": resourceNetworkingSubnetV2(),
|
||||||
|
"openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(),
|
||||||
|
"openstack_networking_router_v2": resourceNetworkingRouterV2(),
|
||||||
|
"openstack_networking_router_interface_v2": resourceNetworkingRouterInterfaceV2(),
|
||||||
|
"openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: configureProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureProvider(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
config := Config{
|
||||||
|
IdentityEndpoint: d.Get("auth_url").(string),
|
||||||
|
Username: d.Get("user_name").(string),
|
||||||
|
UserID: d.Get("user_id").(string),
|
||||||
|
Password: d.Get("password").(string),
|
||||||
|
APIKey: d.Get("api_key").(string),
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
TenantName: d.Get("tenant_name").(string),
|
||||||
|
DomainID: d.Get("domain_id").(string),
|
||||||
|
DomainName: d.Get("domain_name").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.loadAndValidate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func envDefaultFunc(k string) schema.SchemaDefaultFunc {
|
||||||
|
return func() (interface{}, error) {
|
||||||
|
if v := os.Getenv(k); v != "" {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
OS_REGION_NAME = ""
|
||||||
|
OS_POOL_NAME = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"openstack": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
v := os.Getenv("OS_AUTH_URL")
|
||||||
|
if v == "" {
|
||||||
|
t.Fatal("OS_AUTH_URL must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
v = os.Getenv("OS_REGION_NAME")
|
||||||
|
if v == "" {
|
||||||
|
t.Fatal("OS_REGION_NAME must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
OS_REGION_NAME = v
|
||||||
|
|
||||||
|
v1 := os.Getenv("OS_IMAGE_ID")
|
||||||
|
v2 := os.Getenv("OS_IMAGE_NAME")
|
||||||
|
|
||||||
|
if v1 == "" && v2 == "" {
|
||||||
|
t.Fatal("OS_IMAGE_ID or OS_IMAGE_NAME must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
v = os.Getenv("OS_POOL_NAME")
|
||||||
|
if v == "" {
|
||||||
|
t.Fatal("OS_POOL_NAME must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
OS_POOL_NAME = v
|
||||||
|
|
||||||
|
v1 = os.Getenv("OS_FLAVOR_ID")
|
||||||
|
v2 = os.Getenv("OS_FLAVOR_NAME")
|
||||||
|
if v1 == "" && v2 == "" {
|
||||||
|
t.Fatal("OS_FLAVOR_ID or OS_FLAVOR_NAME must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,314 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"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 {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceBlockStorageVolumeV1Create,
|
||||||
|
Read: resourceBlockStorageVolumeV1Read,
|
||||||
|
Update: resourceBlockStorageVolumeV1Update,
|
||||||
|
Delete: resourceBlockStorageVolumeV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"size": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"metadata": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"snapshot_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"source_vol_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"image_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"volume_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"attachment": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Computed: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"instance_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"device": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: resourceVolumeAttachmentHash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := &volumes.CreateOpts{
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Size: d.Get("size").(int),
|
||||||
|
SnapshotID: d.Get("snapshot_id").(string),
|
||||||
|
SourceVolID: d.Get("source_vol_id").(string),
|
||||||
|
ImageID: d.Get("image_id").(string),
|
||||||
|
VolumeType: d.Get("volume_type").(string),
|
||||||
|
Metadata: resourceContainerMetadataV2(d),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
v, err := volumes.Create(blockStorageClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack volume: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Volume ID: %s", v.ID)
|
||||||
|
|
||||||
|
// Store the ID now
|
||||||
|
d.SetId(v.ID)
|
||||||
|
|
||||||
|
// Wait for the volume to become available.
|
||||||
|
log.Printf(
|
||||||
|
"[DEBUG] Waiting for volume (%s) to become available",
|
||||||
|
v.ID)
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Target: "available",
|
||||||
|
Refresh: VolumeV1StateRefreshFunc(blockStorageClient, v.ID),
|
||||||
|
Timeout: 10 * time.Minute,
|
||||||
|
Delay: 10 * time.Second,
|
||||||
|
MinTimeout: 3 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error waiting for volume (%s) to become ready: %s",
|
||||||
|
v.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceBlockStorageVolumeV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "volume")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v)
|
||||||
|
|
||||||
|
d.Set("size", v.Size)
|
||||||
|
d.Set("description", v.Description)
|
||||||
|
d.Set("name", v.Name)
|
||||||
|
d.Set("snapshot_id", v.SnapshotID)
|
||||||
|
d.Set("source_vol_id", v.SourceVolID)
|
||||||
|
d.Set("volume_type", v.VolumeType)
|
||||||
|
d.Set("metadata", v.Metadata)
|
||||||
|
|
||||||
|
if len(v.Attachments) > 0 {
|
||||||
|
attachments := make([]map[string]interface{}, len(v.Attachments))
|
||||||
|
for i, attachment := range v.Attachments {
|
||||||
|
attachments[i] = make(map[string]interface{})
|
||||||
|
attachments[i]["id"] = attachment["id"]
|
||||||
|
attachments[i]["instance_id"] = attachment["server_id"]
|
||||||
|
attachments[i]["device"] = attachment["device"]
|
||||||
|
log.Printf("[DEBUG] attachment: %v", attachment)
|
||||||
|
}
|
||||||
|
d.Set("attachment", attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceBlockStorageVolumeV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOpts := volumes.UpdateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("metadata") {
|
||||||
|
updateOpts.Metadata = resourceVolumeMetadataV1(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack volume: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceBlockStorageVolumeV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := volumes.Get(blockStorageClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "volume")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure this volume is detached from all instances before deleting
|
||||||
|
if len(v.Attachments) > 0 {
|
||||||
|
log.Printf("[DEBUG] detaching volumes")
|
||||||
|
if computeClient, err := config.computeV2Client(d.Get("region").(string)); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
for _, volumeAttachment := range v.Attachments {
|
||||||
|
log.Printf("[DEBUG] Attachment: %v", volumeAttachment)
|
||||||
|
if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"in-use", "attaching"},
|
||||||
|
Target: "available",
|
||||||
|
Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()),
|
||||||
|
Timeout: 10 * time.Minute,
|
||||||
|
Delay: 10 * time.Second,
|
||||||
|
MinTimeout: 3 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error waiting for volume (%s) to become available: %s",
|
||||||
|
d.Id(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = volumes.Delete(blockStorageClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack volume: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the volume to delete before moving on.
|
||||||
|
log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id())
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"deleting", "available"},
|
||||||
|
Target: "deleted",
|
||||||
|
Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()),
|
||||||
|
Timeout: 10 * time.Minute,
|
||||||
|
Delay: 10 * time.Second,
|
||||||
|
MinTimeout: 3 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error waiting for volume (%s) to delete: %s",
|
||||||
|
d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceVolumeMetadataV1(d *schema.ResourceData) map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for key, val := range d.Get("metadata").(map[string]interface{}) {
|
||||||
|
m[key] = val.(string)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||||
|
// an OpenStack volume.
|
||||||
|
func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
v, err := volumes.Get(client, volumeID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if errCode.Actual == 404 {
|
||||||
|
return v, "deleted", nil
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, v.Status, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceVolumeAttachmentHash(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
if m["instance_id"] != nil {
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string)))
|
||||||
|
}
|
||||||
|
return hashcode.String(buf.String())
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccBlockStorageV1Volume_basic(t *testing.T) {
|
||||||
|
var volume volumes.Volume
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckBlockStorageV1VolumeDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccBlockStorageV1Volume_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckBlockStorageV1VolumeExists(t, "openstack_blockstorage_volume_v1.volume_1", &volume),
|
||||||
|
resource.TestCheckResourceAttr("openstack_blockstorage_volume_v1.volume_1", "name", "tf-test-volume"),
|
||||||
|
testAccCheckBlockStorageV1VolumeMetadata(&volume, "foo", "bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccBlockStorageV1Volume_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_blockstorage_volume_v1.volume_1", "name", "tf-test-volume-updated"),
|
||||||
|
testAccCheckBlockStorageV1VolumeMetadata(&volume, "foo", "bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckBlockStorageV1VolumeDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
blockStorageClient, err := config.blockStorageV1Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_blockstorage_volume_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := volumes.Get(blockStorageClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Volume still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckBlockStorageV1VolumeExists(t *testing.T, n string, volume *volumes.Volume) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
blockStorageClient, err := config.blockStorageV1Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack block storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := volumes.Get(blockStorageClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Volume not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*volume = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckBlockStorageV1VolumeMetadata(
|
||||||
|
volume *volumes.Volume, k string, v string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if volume.Metadata == nil {
|
||||||
|
return fmt.Errorf("No metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range volume.Metadata {
|
||||||
|
if k != key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v == value {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Bad value for %s: %s", k, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Metadata not found: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccBlockStorageV1Volume_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_blockstorage_volume_v1" "volume_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf-test-volume"
|
||||||
|
description = "first test volume"
|
||||||
|
metadata{
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
size = 1
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccBlockStorageV1Volume_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_blockstorage_volume_v1" "volume_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf-test-volume-updated"
|
||||||
|
description = "first test volume"
|
||||||
|
metadata{
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
size = 1
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,107 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeFloatingIPV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeFloatingIPV2Create,
|
||||||
|
Read: resourceComputeFloatingIPV2Read,
|
||||||
|
Update: nil,
|
||||||
|
Delete: resourceComputeFloatingIPV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
|
||||||
|
"pool": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_POOL_NAME"),
|
||||||
|
},
|
||||||
|
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"fixed_ip": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"instance_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFloatingIPV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := &floatingip.CreateOpts{
|
||||||
|
Pool: d.Get("pool").(string),
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
newFip, err := floatingip.Create(computeClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating Floating IP: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(newFip.ID)
|
||||||
|
|
||||||
|
return resourceComputeFloatingIPV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fip, err := floatingip.Get(computeClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "floating ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retrieved Floating IP %s: %+v", d.Id(), fip)
|
||||||
|
|
||||||
|
d.Set("pool", fip.Pool)
|
||||||
|
d.Set("instance_id", fip.InstanceID)
|
||||||
|
d.Set("address", fip.IP)
|
||||||
|
d.Set("fixed_ip", fip.FixedIP)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Deleting Floating IP %s", d.Id())
|
||||||
|
if err := floatingip.Delete(computeClient, d.Id()).ExtractErr(); err != nil {
|
||||||
|
return fmt.Errorf("Error deleting Floating IP: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeV2FloatingIP_basic(t *testing.T) {
|
||||||
|
var floatingIP floatingip.FloatingIP
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeV2FloatingIPDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeV2FloatingIP_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.foo", &floatingIP),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2FloatingIPDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2FloatingIPDestroy) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_compute_floatingip_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := floatingip.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("FloatingIP still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingip.FloatingIP) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2FloatingIPExists) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := floatingip.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("FloatingIP not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*kp = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccComputeV2FloatingIP_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_compute_floatingip_v2" "foo" {
|
||||||
|
}`)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,185 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
"github.com/rackspace/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeV2Instance_basic(t *testing.T) {
|
||||||
|
var instance servers.Server
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeV2InstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeV2Instance_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
|
||||||
|
testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeV2Instance_volumeAttach(t *testing.T) {
|
||||||
|
var instance servers.Server
|
||||||
|
var volume volumes.Volume
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeV2InstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeV2Instance_volumeAttach,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckBlockStorageV1VolumeExists(t, "openstack_blockstorage_volume_v1.myvol", &volume),
|
||||||
|
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
|
||||||
|
testAccCheckComputeV2InstanceVolumeAttachment(&instance, &volume),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2InstanceDestroy) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_compute_instance_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := servers.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Instance still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2InstanceExists(t *testing.T, n string, instance *servers.Server) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2InstanceExists) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := servers.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Instance not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*instance = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2InstanceMetadata(
|
||||||
|
instance *servers.Server, k string, v string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if instance.Metadata == nil {
|
||||||
|
return fmt.Errorf("No metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range instance.Metadata {
|
||||||
|
if k != key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v == value.(string) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Bad value for %s: %s", k, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Metadata not found: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2InstanceVolumeAttachment(
|
||||||
|
instance *servers.Server, volume *volumes.Volume) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
var attachments []volumeattach.VolumeAttachment
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = volumeattach.List(computeClient, instance.ID).EachPage(func(page pagination.Page) (bool, error) {
|
||||||
|
actual, err := volumeattach.ExtractVolumeAttachments(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("Unable to lookup attachment: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments = actual
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, attachment := range attachments {
|
||||||
|
if attachment.VolumeID == volume.ID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Volume not found: %s", volume.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccComputeV2Instance_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_compute_instance_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
name = "terraform-test"
|
||||||
|
metadata {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccComputeV2Instance_volumeAttach = fmt.Sprintf(`
|
||||||
|
resource "openstack_blockstorage_volume_v1" "myvol" {
|
||||||
|
name = "myvol"
|
||||||
|
size = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_compute_instance_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
name = "terraform-test"
|
||||||
|
volume {
|
||||||
|
volume_id = "${openstack_blockstorage_volume_v1.myvol.id}"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,92 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeKeypairV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeKeypairV2Create,
|
||||||
|
Read: resourceComputeKeypairV2Read,
|
||||||
|
Delete: resourceComputeKeypairV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"public_key": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeKeypairV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := keypairs.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
PublicKey: d.Get("public_key").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
kp, err := keypairs.Create(computeClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack keypair: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(kp.Name)
|
||||||
|
|
||||||
|
return resourceComputeKeypairV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kp, err := keypairs.Get(computeClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "keypair")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", kp.Name)
|
||||||
|
d.Set("public_key", kp.PublicKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeKeypairV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = keypairs.Delete(computeClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack keypair: %s", err)
|
||||||
|
}
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeV2Keypair_basic(t *testing.T) {
|
||||||
|
var keypair keypairs.KeyPair
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeV2KeypairDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeV2Keypair_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeV2KeypairExists(t, "openstack_compute_keypair_v2.foo", &keypair),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2KeypairDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2KeypairDestroy) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_compute_keypair_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := keypairs.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Keypair still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2KeypairExists(t *testing.T, n string, kp *keypairs.KeyPair) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2KeypairExists) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := keypairs.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Keypair not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*kp = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccComputeV2Keypair_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_compute_keypair_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
name = "test-keypair-tf"
|
||||||
|
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAjpC1hwiOCCmKEWxJ4qzTTsJbKzndLo1BCz5PcwtUnflmU+gHJtWMZKpuEGVi29h0A/+ydKek1O18k10Ff+4tyFjiHDQAT9+OfgWf7+b1yK+qDip3X1C0UPMbwHlTfSGWLGZquwhvEFx9k3h/M+VtMvwR1lJ9LUyTAImnNjWG7TAIPmui30HvM2UiFEmqkr4ijq45MyX2+fLIePLRIFuu1p4whjHAQYufqyno3BS48icQb4p6iVEZPo4AE2o9oIyQvj2mx4dk5Y8CgSETOZTYDOR3rU2fZTRDRgPJDH9FWvQjF5tA0p3d9CoWWd2s6GKKbfoUIi8R/Db1BSPJwkqB jrp-hp-pc"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,294 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeSecGroupV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeSecGroupV2Create,
|
||||||
|
Read: resourceComputeSecGroupV2Read,
|
||||||
|
Update: resourceComputeSecGroupV2Update,
|
||||||
|
Delete: resourceComputeSecGroupV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"rule": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"from_port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"to_port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"ip_protocol": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"cidr": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"from_group_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"self": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := secgroups.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
sg, err := secgroups.Create(computeClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack security group: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(sg.ID)
|
||||||
|
|
||||||
|
createRuleOptsList := resourceSecGroupRulesV2(d)
|
||||||
|
for _, createRuleOpts := range createRuleOptsList {
|
||||||
|
_, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack security group rule: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeSecGroupV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sg, err := secgroups.Get(computeClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "security group")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", sg.Name)
|
||||||
|
d.Set("description", sg.Description)
|
||||||
|
rtm := rulesToMap(sg.Rules)
|
||||||
|
for _, v := range rtm {
|
||||||
|
if v["group"] == d.Get("name") {
|
||||||
|
v["self"] = "1"
|
||||||
|
} else {
|
||||||
|
v["self"] = "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm)
|
||||||
|
d.Set("rule", rtm)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOpts := secgroups.UpdateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = secgroups.Update(computeClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("rule") {
|
||||||
|
oldSGRaw, newSGRaw := d.GetChange("rule")
|
||||||
|
oldSGRSlice, newSGRSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{})
|
||||||
|
oldSGRSet := schema.NewSet(secgroupRuleV2Hash, oldSGRSlice)
|
||||||
|
newSGRSet := schema.NewSet(secgroupRuleV2Hash, newSGRSlice)
|
||||||
|
secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet)
|
||||||
|
secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Security groups rules to remove: %v", secgrouprulesToRemove)
|
||||||
|
|
||||||
|
for _, rawRule := range secgrouprulesToAdd.List() {
|
||||||
|
createRuleOpts := resourceSecGroupRuleCreateOptsV2(d, rawRule)
|
||||||
|
rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Added rule (%s) to OpenStack security group (%s) ", rule.ID, d.Id())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range secgrouprulesToRemove.List() {
|
||||||
|
rule := resourceSecGroupRuleV2(d, r)
|
||||||
|
err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeSecGroupV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = secgroups.Delete(computeClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack security group: %s", err)
|
||||||
|
}
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts {
|
||||||
|
rawRules := (d.Get("rule")).([]interface{})
|
||||||
|
createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules))
|
||||||
|
for i, raw := range rawRules {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
groupId := rawMap["from_group_id"].(string)
|
||||||
|
if rawMap["self"].(bool) {
|
||||||
|
groupId = d.Id()
|
||||||
|
}
|
||||||
|
createRuleOptsList[i] = secgroups.CreateRuleOpts{
|
||||||
|
ParentGroupID: d.Id(),
|
||||||
|
FromPort: rawMap["from_port"].(int),
|
||||||
|
ToPort: rawMap["to_port"].(int),
|
||||||
|
IPProtocol: rawMap["ip_protocol"].(string),
|
||||||
|
CIDR: rawMap["cidr"].(string),
|
||||||
|
FromGroupID: groupId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createRuleOptsList
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
groupId := rawMap["from_group_id"].(string)
|
||||||
|
if rawMap["self"].(bool) {
|
||||||
|
groupId = d.Id()
|
||||||
|
}
|
||||||
|
return secgroups.CreateRuleOpts{
|
||||||
|
ParentGroupID: d.Id(),
|
||||||
|
FromPort: rawMap["from_port"].(int),
|
||||||
|
ToPort: rawMap["to_port"].(int),
|
||||||
|
IPProtocol: rawMap["ip_protocol"].(string),
|
||||||
|
CIDR: rawMap["cidr"].(string),
|
||||||
|
FromGroupID: groupId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.Rule {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
return secgroups.Rule{
|
||||||
|
ID: rawMap["id"].(string),
|
||||||
|
ParentGroupID: d.Id(),
|
||||||
|
FromPort: rawMap["from_port"].(int),
|
||||||
|
ToPort: rawMap["to_port"].(int),
|
||||||
|
IPProtocol: rawMap["ip_protocol"].(string),
|
||||||
|
IPRange: secgroups.IPRange{CIDR: rawMap["cidr"].(string)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} {
|
||||||
|
sgrMap := make([]map[string]interface{}, len(sgrs))
|
||||||
|
for i, sgr := range sgrs {
|
||||||
|
sgrMap[i] = map[string]interface{}{
|
||||||
|
"id": sgr.ID,
|
||||||
|
"from_port": sgr.FromPort,
|
||||||
|
"to_port": sgr.ToPort,
|
||||||
|
"ip_protocol": sgr.IPProtocol,
|
||||||
|
"cidr": sgr.IPRange.CIDR,
|
||||||
|
"group": sgr.Group.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sgrMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func secgroupRuleV2Hash(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["cidr"].(string)))
|
||||||
|
|
||||||
|
return hashcode.String(buf.String())
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeV2SecGroup_basic(t *testing.T) {
|
||||||
|
var secgroup secgroups.SecurityGroup
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeV2SecGroupDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeV2SecGroup_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeV2SecGroupExists(t, "openstack_compute_secgroup_v2.foo", &secgroup),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2SecGroupDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2SecGroupDestroy) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_compute_secgroup_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := secgroups.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Security group still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeV2SecGroupExists(t *testing.T, n string, secgroup *secgroups.SecurityGroup) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
computeClient, err := config.computeV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckComputeV2SecGroupExists) Error creating OpenStack compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := secgroups.Get(computeClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Security group not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*secgroup = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccComputeV2SecGroup_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_compute_secgroup_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
name = "test_group_1"
|
||||||
|
description = "first test security group"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,242 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceFWFirewallV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceFWFirewallV1Create,
|
||||||
|
Read: resourceFWFirewallV1Read,
|
||||||
|
Update: resourceFWFirewallV1Update,
|
||||||
|
Delete: resourceFWFirewallV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"policy_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"admin_state_up": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminStateUp := d.Get("admin_state_up").(bool)
|
||||||
|
|
||||||
|
firewallConfiguration := firewalls.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
PolicyID: d.Get("policy_id").(string),
|
||||||
|
AdminStateUp: &adminStateUp,
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create firewall: %#v", firewallConfiguration)
|
||||||
|
|
||||||
|
firewall, err := firewalls.Create(networkingClient, firewallConfiguration).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Firewall created: %#v", firewall)
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"PENDING_CREATE"},
|
||||||
|
Target: "ACTIVE",
|
||||||
|
Refresh: waitForFirewallActive(networkingClient, firewall.ID),
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
Delay: 0,
|
||||||
|
MinTimeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
|
||||||
|
d.SetId(firewall.ID)
|
||||||
|
|
||||||
|
return resourceFWFirewallV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[DEBUG] Retrieve information about firewall: %s", d.Id())
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
firewall, err := firewalls.Get(networkingClient, d.Id()).Extract()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "LB pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", firewall.Name)
|
||||||
|
d.Set("description", firewall.Description)
|
||||||
|
d.Set("policy_id", firewall.PolicyID)
|
||||||
|
d.Set("admin_state_up", firewall.AdminStateUp)
|
||||||
|
d.Set("tenant_id", firewall.TenantID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := firewalls.UpdateOpts{}
|
||||||
|
|
||||||
|
if d.HasChange("name") {
|
||||||
|
opts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("description") {
|
||||||
|
opts.Description = d.Get("description").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("policy_id") {
|
||||||
|
opts.PolicyID = d.Get("policy_id").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("admin_state_up") {
|
||||||
|
adminStateUp := d.Get("admin_state_up").(bool)
|
||||||
|
opts.AdminStateUp = &adminStateUp
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating firewall with id %s: %#v", d.Id(), opts)
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"},
|
||||||
|
Target: "ACTIVE",
|
||||||
|
Refresh: waitForFirewallActive(networkingClient, d.Id()),
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
Delay: 0,
|
||||||
|
MinTimeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
|
||||||
|
err = firewalls.Update(networkingClient, d.Id(), opts).Err
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceFWFirewallV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWFirewallV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[DEBUG] Destroy firewall: %s", d.Id())
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"},
|
||||||
|
Target: "ACTIVE",
|
||||||
|
Refresh: waitForFirewallActive(networkingClient, d.Id()),
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
Delay: 0,
|
||||||
|
MinTimeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
|
||||||
|
err = firewalls.Delete(networkingClient, d.Id()).Err
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateConf = &resource.StateChangeConf{
|
||||||
|
Pending: []string{"DELETING"},
|
||||||
|
Target: "DELETED",
|
||||||
|
Refresh: waitForFirewallDeletion(networkingClient, d.Id()),
|
||||||
|
Timeout: 2 * time.Minute,
|
||||||
|
Delay: 0,
|
||||||
|
MinTimeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stateConf.WaitForState()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForFirewallActive(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc {
|
||||||
|
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
fw, err := firewalls.Get(networkingClient, id).Extract()
|
||||||
|
log.Printf("[DEBUG] Get firewall %s => %#v", id, fw)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return fw, fw.Status, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc {
|
||||||
|
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
fw, err := firewalls.Get(networkingClient, id).Extract()
|
||||||
|
log.Printf("[DEBUG] Get firewall %s => %#v", id, fw)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
httpStatus := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
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)
|
||||||
|
return "", "DELETED", nil
|
||||||
|
}
|
||||||
|
return nil, "", fmt.Errorf("Unexpected status code %d", httpStatus.Actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Firewall %s deletion is pending", id)
|
||||||
|
return fw, "DELETING", nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccFWFirewallV1(t *testing.T) {
|
||||||
|
|
||||||
|
var policyID *string
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckFWFirewallV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.accept_test", "", "", policyID),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallConfigUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.accept_test", "accept_test", "terraform acceptance test", policyID),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFWFirewallV1Destroy(s *terraform.State) error {
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckOpenstackFirewallDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_firewall" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Firewall (%s) still exists.", rs.Primary.ID)
|
||||||
|
}
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 404 {
|
||||||
|
return httpError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFWFirewallV1Exists(n, expectedName, expectedDescription string, policyID *string) resource.TestCheckFunc {
|
||||||
|
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckFirewallExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *firewalls.Firewall
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
// Firewall creation is asynchronous. Retry some times
|
||||||
|
// if we get a 404 error. Fail on any other error.
|
||||||
|
found, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 404 {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != expectedName {
|
||||||
|
return fmt.Errorf("Expected Name to be <%s> but found <%s>", expectedName, found.Name)
|
||||||
|
}
|
||||||
|
if found.Description != expectedDescription {
|
||||||
|
return fmt.Errorf("Expected Description to be <%s> but found <%s>", expectedDescription, found.Description)
|
||||||
|
}
|
||||||
|
if found.PolicyID == "" {
|
||||||
|
return fmt.Errorf("Policy should not be empty")
|
||||||
|
}
|
||||||
|
if policyID != nil && found.PolicyID == *policyID {
|
||||||
|
return fmt.Errorf("Policy had not been correctly updated. Went from <%s> to <%s>", expectedName, found.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
policyID = &found.PolicyID
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFirewallConfig = `
|
||||||
|
resource "openstack_fw_firewall_v1" "accept_test" {
|
||||||
|
policy_id = "${openstack_fw_policy_v1.accept_test_policy_1.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_fw_policy_v1" "accept_test_policy_1" {
|
||||||
|
name = "policy-1"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testFirewallConfigUpdated = `
|
||||||
|
resource "openstack_fw_firewall_v1" "accept_test" {
|
||||||
|
name = "accept_test"
|
||||||
|
description = "terraform acceptance test"
|
||||||
|
policy_id = "${openstack_fw_policy_v1.accept_test_policy_2.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_fw_policy_v1" "accept_test_policy_2" {
|
||||||
|
name = "policy-2"
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,200 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceFWPolicyV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceFWPolicyV1Create,
|
||||||
|
Read: resourceFWPolicyV1Read,
|
||||||
|
Update: resourceFWPolicyV1Update,
|
||||||
|
Delete: resourceFWPolicyV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"audited": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"shared": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"rules": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := d.Get("rules").(*schema.Set)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Rules found : %#v", v)
|
||||||
|
log.Printf("[DEBUG] Rules count : %d", v.Len())
|
||||||
|
|
||||||
|
rules := make([]string, v.Len())
|
||||||
|
for i, v := range v.List() {
|
||||||
|
rules[i] = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
audited := d.Get("audited").(bool)
|
||||||
|
shared := d.Get("shared").(bool)
|
||||||
|
|
||||||
|
opts := policies.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
Audited: &audited,
|
||||||
|
Shared: &shared,
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
Rules: rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create firewall policy: %#v", opts)
|
||||||
|
|
||||||
|
policy, err := policies.Create(networkingClient, opts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Firewall policy created: %#v", policy)
|
||||||
|
|
||||||
|
d.SetId(policy.ID)
|
||||||
|
|
||||||
|
return resourceFWPolicyV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[DEBUG] Retrieve information about firewall policy: %s", d.Id())
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := policies.Get(networkingClient, d.Id()).Extract()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "LB pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", policy.Name)
|
||||||
|
d.Set("description", policy.Description)
|
||||||
|
d.Set("shared", policy.Shared)
|
||||||
|
d.Set("audited", policy.Audited)
|
||||||
|
d.Set("tenant_id", policy.TenantID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWPolicyV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := policies.UpdateOpts{}
|
||||||
|
|
||||||
|
if d.HasChange("name") {
|
||||||
|
opts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("description") {
|
||||||
|
opts.Description = d.Get("description").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("rules") {
|
||||||
|
v := d.Get("rules").(*schema.Set)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Rules found : %#v", v)
|
||||||
|
log.Printf("[DEBUG] Rules count : %d", v.Len())
|
||||||
|
|
||||||
|
rules := make([]string, v.Len())
|
||||||
|
for i, v := range v.List() {
|
||||||
|
rules[i] = v.(string)
|
||||||
|
}
|
||||||
|
opts.Rules = rules
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating firewall policy with id %s: %#v", d.Id(), opts)
|
||||||
|
|
||||||
|
err = policies.Update(networkingClient, d.Id(), opts).Err
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceFWPolicyV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWPolicyV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[DEBUG] Destroy firewall policy: %s", d.Id())
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 15; i++ {
|
||||||
|
|
||||||
|
err = policies.Delete(networkingClient, d.Id()).Err
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 409 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This error usualy means that the policy is attached
|
||||||
|
// to a firewall. At this point, the firewall is probably
|
||||||
|
// being delete. So, we retry a few times.
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccFWPolicyV1(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckFWPolicyV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallPolicyConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWPolicyV1Exists(
|
||||||
|
"openstack_fw_policy_v1.accept_test",
|
||||||
|
"", "", 0),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallPolicyConfigAddRules,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWPolicyV1Exists(
|
||||||
|
"openstack_fw_policy_v1.accept_test",
|
||||||
|
"accept_test", "terraform acceptance test", 2),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallPolicyUpdateDeleteRule,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWPolicyV1Exists(
|
||||||
|
"openstack_fw_policy_v1.accept_test",
|
||||||
|
"accept_test", "terraform acceptance test", 1),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFWPolicyV1Destroy(s *terraform.State) error {
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckOpenstackFirewallPolicyDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_fw_policy_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = policies.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Firewall policy (%s) still exists.", rs.Primary.ID)
|
||||||
|
}
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 404 {
|
||||||
|
return httpError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFWPolicyV1Exists(n, name, description string, ruleCount int) resource.TestCheckFunc {
|
||||||
|
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckFirewallPolicyExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *policies.Policy
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
// Firewall policy creation is asynchronous. Retry some times
|
||||||
|
// if we get a 404 error. Fail on any other error.
|
||||||
|
found, err = policies.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 404 {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != found.Name {
|
||||||
|
return fmt.Errorf("Expected name <%s>, but found <%s>", name, found.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if description != found.Description {
|
||||||
|
return fmt.Errorf("Expected description <%s>, but found <%s>", description, found.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleCount != len(found.Rules) {
|
||||||
|
return fmt.Errorf("Expected rule count <%d>, but found <%d>", ruleCount, len(found.Rules))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFirewallPolicyConfig = `
|
||||||
|
resource "openstack_fw_policy_v1" "accept_test" {
|
||||||
|
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testFirewallPolicyConfigAddRules = `
|
||||||
|
resource "openstack_fw_policy_v1" "accept_test" {
|
||||||
|
name = "accept_test"
|
||||||
|
description = "terraform acceptance test"
|
||||||
|
rules = [
|
||||||
|
"${openstack_fw_rule_v1.accept_test_udp_deny.id}",
|
||||||
|
"${openstack_fw_rule_v1.accept_test_tcp_allow.id}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_fw_rule_v1" "accept_test_tcp_allow" {
|
||||||
|
protocol = "tcp"
|
||||||
|
action = "allow"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_fw_rule_v1" "accept_test_udp_deny" {
|
||||||
|
protocol = "udp"
|
||||||
|
action = "deny"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testFirewallPolicyUpdateDeleteRule = `
|
||||||
|
resource "openstack_fw_policy_v1" "accept_test" {
|
||||||
|
name = "accept_test"
|
||||||
|
description = "terraform acceptance test"
|
||||||
|
rules = [
|
||||||
|
"${openstack_fw_rule_v1.accept_test_udp_deny.id}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_fw_rule_v1" "accept_test_udp_deny" {
|
||||||
|
protocol = "udp"
|
||||||
|
action = "deny"
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,223 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"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 {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceFWRuleV1Create,
|
||||||
|
Read: resourceFWRuleV1Read,
|
||||||
|
Update: resourceFWRuleV1Update,
|
||||||
|
Delete: resourceFWRuleV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"protocol": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"action": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"ip_version": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: 4,
|
||||||
|
},
|
||||||
|
"source_ip_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"destination_ip_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"source_port": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"destination_port": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"enabled": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWRuleV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled := d.Get("enabled").(bool)
|
||||||
|
|
||||||
|
ruleConfiguration := rules.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
Protocol: d.Get("protocol").(string),
|
||||||
|
Action: d.Get("action").(string),
|
||||||
|
IPVersion: d.Get("ip_version").(int),
|
||||||
|
SourceIPAddress: d.Get("source_ip_address").(string),
|
||||||
|
DestinationIPAddress: d.Get("destination_ip_address").(string),
|
||||||
|
SourcePort: d.Get("source_port").(string),
|
||||||
|
DestinationPort: d.Get("destination_port").(string),
|
||||||
|
Enabled: &enabled,
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create firewall rule: %#v", ruleConfiguration)
|
||||||
|
|
||||||
|
rule, err := rules.Create(networkingClient, ruleConfiguration).Extract()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Firewall rule with id %s : %#v", rule.ID, rule)
|
||||||
|
|
||||||
|
d.SetId(rule.ID)
|
||||||
|
|
||||||
|
return resourceFWRuleV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWRuleV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[DEBUG] Retrieve information about firewall rule: %s", d.Id())
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := rules.Get(networkingClient, d.Id()).Extract()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "LB pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("protocol", rule.Protocol)
|
||||||
|
d.Set("action", rule.Action)
|
||||||
|
|
||||||
|
d.Set("name", rule.Name)
|
||||||
|
d.Set("description", rule.Description)
|
||||||
|
d.Set("ip_version", rule.IPVersion)
|
||||||
|
d.Set("source_ip_address", rule.SourceIPAddress)
|
||||||
|
d.Set("destination_ip_address", rule.DestinationIPAddress)
|
||||||
|
d.Set("source_port", rule.SourcePort)
|
||||||
|
d.Set("destination_port", rule.DestinationPort)
|
||||||
|
d.Set("enabled", rule.Enabled)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWRuleV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := rules.UpdateOpts{}
|
||||||
|
|
||||||
|
if d.HasChange("name") {
|
||||||
|
opts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("description") {
|
||||||
|
opts.Description = d.Get("description").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("protocol") {
|
||||||
|
opts.Protocol = d.Get("protocol").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("action") {
|
||||||
|
opts.Action = d.Get("action").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("ip_version") {
|
||||||
|
opts.IPVersion = d.Get("ip_version").(int)
|
||||||
|
}
|
||||||
|
if d.HasChange("source_ip_address") {
|
||||||
|
sourceIPAddress := d.Get("source_ip_address").(string)
|
||||||
|
opts.SourceIPAddress = &sourceIPAddress
|
||||||
|
}
|
||||||
|
if d.HasChange("destination_ip_address") {
|
||||||
|
destinationIPAddress := d.Get("destination_ip_address").(string)
|
||||||
|
opts.DestinationIPAddress = &destinationIPAddress
|
||||||
|
}
|
||||||
|
if d.HasChange("source_port") {
|
||||||
|
sourcePort := d.Get("source_port").(string)
|
||||||
|
opts.SourcePort = &sourcePort
|
||||||
|
}
|
||||||
|
if d.HasChange("destination_port") {
|
||||||
|
destinationPort := d.Get("destination_port").(string)
|
||||||
|
opts.DestinationPort = &destinationPort
|
||||||
|
}
|
||||||
|
if d.HasChange("enabled") {
|
||||||
|
enabled := d.Get("enabled").(bool)
|
||||||
|
opts.Enabled = &enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating firewall rules: %#v", opts)
|
||||||
|
|
||||||
|
err = rules.Update(networkingClient, d.Id(), opts).Err
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceFWRuleV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFWRuleV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
log.Printf("[DEBUG] Destroy firewall rule: %s", d.Id())
|
||||||
|
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := rules.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.PolicyID != "" {
|
||||||
|
err := policies.RemoveRule(networkingClient, rule.PolicyID, rule.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules.Delete(networkingClient, d.Id()).Err
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccFWRuleV1(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckFWRuleV1Destroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallRuleMinimalConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWRuleV1Exists(
|
||||||
|
"openstack_fw_rule_v1.accept_test_minimal",
|
||||||
|
&rules.Rule{
|
||||||
|
Protocol: "udp",
|
||||||
|
Action: "deny",
|
||||||
|
IPVersion: 4,
|
||||||
|
Enabled: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallRuleConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWRuleV1Exists(
|
||||||
|
"openstack_fw_rule_v1.accept_test",
|
||||||
|
&rules.Rule{
|
||||||
|
Name: "accept_test",
|
||||||
|
Protocol: "udp",
|
||||||
|
Action: "deny",
|
||||||
|
Description: "Terraform accept test",
|
||||||
|
IPVersion: 4,
|
||||||
|
SourceIPAddress: "1.2.3.4",
|
||||||
|
DestinationIPAddress: "4.3.2.0/24",
|
||||||
|
SourcePort: "444",
|
||||||
|
DestinationPort: "555",
|
||||||
|
Enabled: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testFirewallRuleUpdateAllFieldsConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckFWRuleV1Exists(
|
||||||
|
"openstack_fw_rule_v1.accept_test",
|
||||||
|
&rules.Rule{
|
||||||
|
Name: "accept_test_updated_2",
|
||||||
|
Protocol: "tcp",
|
||||||
|
Action: "allow",
|
||||||
|
Description: "Terraform accept test updated",
|
||||||
|
IPVersion: 4,
|
||||||
|
SourceIPAddress: "1.2.3.0/24",
|
||||||
|
DestinationIPAddress: "4.3.2.8",
|
||||||
|
SourcePort: "666",
|
||||||
|
DestinationPort: "777",
|
||||||
|
Enabled: false,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFWRuleV1Destroy(s *terraform.State) error {
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckOpenstackFirewallRuleDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_firewall_rule" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = rules.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Firewall rule (%s) still exists.", rs.Primary.ID)
|
||||||
|
}
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 404 {
|
||||||
|
return httpError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckFWRuleV1Exists(n string, expected *rules.Rule) resource.TestCheckFunc {
|
||||||
|
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckFirewallRuleExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found *rules.Rule
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
// Firewall rule creation is asynchronous. Retry some times
|
||||||
|
// if we get a 404 error. Fail on any other error.
|
||||||
|
found, err = rules.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok || httpError.Actual != 404 {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
expected.ID = found.ID
|
||||||
|
// Erase the tenant id because we don't want to compare
|
||||||
|
// it as long it is not present in the expected
|
||||||
|
found.TenantID = ""
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, found) {
|
||||||
|
return fmt.Errorf("Expected:\n%#v\nFound:\n%#v", expected, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testFirewallRuleMinimalConfig = `
|
||||||
|
resource "openstack_fw_rule_v1" "accept_test_minimal" {
|
||||||
|
protocol = "udp"
|
||||||
|
action = "deny"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testFirewallRuleConfig = `
|
||||||
|
resource "openstack_fw_rule_v1" "accept_test" {
|
||||||
|
name = "accept_test"
|
||||||
|
description = "Terraform accept test"
|
||||||
|
protocol = "udp"
|
||||||
|
action = "deny"
|
||||||
|
ip_version = 4
|
||||||
|
source_ip_address = "1.2.3.4"
|
||||||
|
destination_ip_address = "4.3.2.0/24"
|
||||||
|
source_port = "444"
|
||||||
|
destination_port = "555"
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testFirewallRuleUpdateAllFieldsConfig = `
|
||||||
|
resource "openstack_fw_rule_v1" "accept_test" {
|
||||||
|
name = "accept_test_updated_2"
|
||||||
|
description = "Terraform accept test updated"
|
||||||
|
protocol = "tcp"
|
||||||
|
action = "allow"
|
||||||
|
ip_version = 4
|
||||||
|
source_ip_address = "1.2.3.0/24"
|
||||||
|
destination_ip_address = "4.3.2.8"
|
||||||
|
source_port = "666"
|
||||||
|
destination_port = "777"
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,192 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceLBMonitorV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceLBMonitorV1Create,
|
||||||
|
Read: resourceLBMonitorV1Read,
|
||||||
|
Update: resourceLBMonitorV1Update,
|
||||||
|
Delete: resourceLBMonitorV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"delay": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"timeout": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"max_retries": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"url_path": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"http_method": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"expected_codes": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"admin_state_up": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := monitors.CreateOpts{
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
Type: d.Get("type").(string),
|
||||||
|
Delay: d.Get("delay").(int),
|
||||||
|
Timeout: d.Get("timeout").(int),
|
||||||
|
MaxRetries: d.Get("max_retries").(int),
|
||||||
|
URLPath: d.Get("url_path").(string),
|
||||||
|
ExpectedCodes: d.Get("expected_codes").(string),
|
||||||
|
HTTPMethod: d.Get("http_method").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
createOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
m, err := monitors.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack LB Monitor: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] LB Monitor ID: %s", m.ID)
|
||||||
|
|
||||||
|
d.SetId(m.ID)
|
||||||
|
|
||||||
|
return resourceLBMonitorV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := monitors.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "LB monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived OpenStack LB Monitor %s: %+v", d.Id(), m)
|
||||||
|
|
||||||
|
d.Set("type", m.Type)
|
||||||
|
d.Set("delay", m.Delay)
|
||||||
|
d.Set("timeout", m.Timeout)
|
||||||
|
d.Set("max_retries", m.MaxRetries)
|
||||||
|
d.Set("tenant_id", m.TenantID)
|
||||||
|
d.Set("url_path", m.URLPath)
|
||||||
|
d.Set("http_method", m.HTTPMethod)
|
||||||
|
d.Set("expected_codes", m.ExpectedCodes)
|
||||||
|
d.Set("admin_state_up", strconv.FormatBool(m.AdminStateUp))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBMonitorV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOpts := monitors.UpdateOpts{
|
||||||
|
Delay: d.Get("delay").(int),
|
||||||
|
Timeout: d.Get("timeout").(int),
|
||||||
|
MaxRetries: d.Get("max_retries").(int),
|
||||||
|
URLPath: d.Get("url_path").(string),
|
||||||
|
HTTPMethod: d.Get("http_method").(string),
|
||||||
|
ExpectedCodes: d.Get("expected_codes").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("admin_state_up") {
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
updateOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating OpenStack LB Monitor %s with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = monitors.Update(networkingClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack LB Monitor: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceLBMonitorV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBMonitorV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = monitors.Delete(networkingClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack LB Monitor: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccLBV1Monitor_basic(t *testing.T) {
|
||||||
|
var monitor monitors.Monitor
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckLBV1MonitorDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccLBV1Monitor_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckLBV1MonitorExists(t, "openstack_lb_monitor_v1.monitor_1", &monitor),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccLBV1Monitor_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_lb_monitor_v1.monitor_1", "delay", "20"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckLBV1MonitorDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckLBV1MonitorDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_lb_monitor_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := monitors.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("LB monitor still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckLBV1MonitorExists(t *testing.T, n string, monitor *monitors.Monitor) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckLBV1MonitorExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := monitors.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Monitor not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*monitor = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccLBV1Monitor_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_lb_monitor_v1" "monitor_1" {
|
||||||
|
region = "%s"
|
||||||
|
type = "PING"
|
||||||
|
delay = 30
|
||||||
|
timeout = 5
|
||||||
|
max_retries = 3
|
||||||
|
admin_state_up = "true"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccLBV1Monitor_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_lb_monitor_v1" "monitor_1" {
|
||||||
|
region = "%s"
|
||||||
|
type = "PING"
|
||||||
|
delay = 20
|
||||||
|
timeout = 5
|
||||||
|
max_retries = 3
|
||||||
|
admin_state_up = "true"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,327 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
|
||||||
|
"github.com/rackspace/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceLBPoolV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceLBPoolV1Create,
|
||||||
|
Read: resourceLBPoolV1Read,
|
||||||
|
Update: resourceLBPoolV1Update,
|
||||||
|
Delete: resourceLBPoolV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"protocol": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"subnet_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"lb_method": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"member": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"admin_state_up": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: resourceLBMemberV1Hash,
|
||||||
|
},
|
||||||
|
"monitor_ids": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := pools.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Protocol: d.Get("protocol").(string),
|
||||||
|
SubnetID: d.Get("subnet_id").(string),
|
||||||
|
LBMethod: d.Get("lb_method").(string),
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
p, err := pools.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack LB pool: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] LB Pool ID: %s", p.ID)
|
||||||
|
|
||||||
|
d.SetId(p.ID)
|
||||||
|
|
||||||
|
if mIDs := resourcePoolMonitorIDsV1(d); mIDs != nil {
|
||||||
|
for _, mID := range mIDs {
|
||||||
|
_, err := pools.AssociateMonitor(networkingClient, p.ID, mID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error associating monitor (%s) with OpenStack LB pool (%s): %s", mID, p.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if memberOpts := resourcePoolMembersV1(d); memberOpts != nil {
|
||||||
|
for _, memberOpt := range memberOpts {
|
||||||
|
_, err := members.Create(networkingClient, memberOpt).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack LB member: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceLBPoolV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := pools.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "LB pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived OpenStack LB Pool %s: %+v", d.Id(), p)
|
||||||
|
|
||||||
|
d.Set("name", p.Name)
|
||||||
|
d.Set("protocol", p.Protocol)
|
||||||
|
d.Set("subnet_id", p.SubnetID)
|
||||||
|
d.Set("lb_method", p.LBMethod)
|
||||||
|
d.Set("tenant_id", p.TenantID)
|
||||||
|
d.Set("monitor_ids", p.MonitorIDs)
|
||||||
|
d.Set("member_ids", p.MemberIDs)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateOpts pools.UpdateOpts
|
||||||
|
if d.HasChange("name") {
|
||||||
|
updateOpts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("lb_method") {
|
||||||
|
updateOpts.LBMethod = d.Get("lb_method").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack LB Pool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("monitor_ids") {
|
||||||
|
oldMIDsRaw, newMIDsRaw := d.GetChange("security_groups")
|
||||||
|
oldMIDsSet, newMIDsSet := oldMIDsRaw.(*schema.Set), newMIDsRaw.(*schema.Set)
|
||||||
|
monitorsToAdd := newMIDsSet.Difference(oldMIDsSet)
|
||||||
|
monitorsToRemove := oldMIDsSet.Difference(newMIDsSet)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Monitors to add: %v", monitorsToAdd)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Monitors to remove: %v", monitorsToRemove)
|
||||||
|
|
||||||
|
for _, m := range monitorsToAdd.List() {
|
||||||
|
_, err := pools.AssociateMonitor(networkingClient, d.Id(), m.(string)).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error associating monitor (%s) with OpenStack server (%s): %s", m.(string), d.Id(), err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Associated monitor (%s) with pool (%s)", m.(string), d.Id())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range monitorsToRemove.List() {
|
||||||
|
_, err := pools.DisassociateMonitor(networkingClient, d.Id(), m.(string)).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error disassociating monitor (%s) from OpenStack server (%s): %s", m.(string), d.Id(), err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Disassociated monitor (%s) from pool (%s)", m.(string), d.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("member") {
|
||||||
|
oldMembersRaw, newMembersRaw := d.GetChange("member")
|
||||||
|
oldMembersSet, newMembersSet := oldMembersRaw.(*schema.Set), newMembersRaw.(*schema.Set)
|
||||||
|
membersToAdd := newMembersSet.Difference(oldMembersSet)
|
||||||
|
membersToRemove := oldMembersSet.Difference(newMembersSet)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Members to add: %v", membersToAdd)
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Members to remove: %v", membersToRemove)
|
||||||
|
|
||||||
|
for _, m := range membersToRemove.List() {
|
||||||
|
oldMember := resourcePoolMemberV1(d, m)
|
||||||
|
listOpts := members.ListOpts{
|
||||||
|
PoolID: d.Id(),
|
||||||
|
Address: oldMember.Address,
|
||||||
|
ProtocolPort: oldMember.ProtocolPort,
|
||||||
|
}
|
||||||
|
err = members.List(networkingClient, listOpts).EachPage(func(page pagination.Page) (bool, error) {
|
||||||
|
extractedMembers, err := members.ExtractMembers(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, member := range extractedMembers {
|
||||||
|
err := members.Delete(networkingClient, member.ID).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("Error deleting member (%s) from OpenStack LB pool (%s): %s", member.ID, d.Id(), err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Deleted member (%s) from pool (%s)", member.ID, d.Id())
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range membersToAdd.List() {
|
||||||
|
createOpts := resourcePoolMemberV1(d, m)
|
||||||
|
newMember, err := members.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating LB member: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Created member (%s) in OpenStack LB pool (%s)", newMember.ID, d.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceLBPoolV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBPoolV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pools.Delete(networkingClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack LB Pool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePoolMonitorIDsV1(d *schema.ResourceData) []string {
|
||||||
|
mIDsRaw := d.Get("monitor_ids").(*schema.Set)
|
||||||
|
mIDs := make([]string, mIDsRaw.Len())
|
||||||
|
for i, raw := range mIDsRaw.List() {
|
||||||
|
mIDs[i] = raw.(string)
|
||||||
|
}
|
||||||
|
return mIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePoolMembersV1(d *schema.ResourceData) []members.CreateOpts {
|
||||||
|
memberOptsRaw := (d.Get("member")).(*schema.Set)
|
||||||
|
memberOpts := make([]members.CreateOpts, memberOptsRaw.Len())
|
||||||
|
for i, raw := range memberOptsRaw.List() {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
memberOpts[i] = members.CreateOpts{
|
||||||
|
TenantID: rawMap["tenant_id"].(string),
|
||||||
|
Address: rawMap["address"].(string),
|
||||||
|
ProtocolPort: rawMap["port"].(int),
|
||||||
|
PoolID: d.Id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return memberOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePoolMemberV1(d *schema.ResourceData, raw interface{}) members.CreateOpts {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
return members.CreateOpts{
|
||||||
|
TenantID: rawMap["tenant_id"].(string),
|
||||||
|
Address: rawMap["address"].(string),
|
||||||
|
ProtocolPort: rawMap["port"].(int),
|
||||||
|
PoolID: d.Id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBMemberV1Hash(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["region"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["tenant_id"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["address"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%d-", m["port"].(int)))
|
||||||
|
|
||||||
|
return hashcode.String(buf.String())
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccLBV1Pool_basic(t *testing.T) {
|
||||||
|
var pool pools.Pool
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckLBV1PoolDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccLBV1Pool_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckLBV1PoolExists(t, "openstack_lb_pool_v1.pool_1", &pool),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccLBV1Pool_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_lb_pool_v1.pool_1", "name", "tf_test_lb_pool_updated"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckLBV1PoolDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckLBV1PoolDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_lb_pool_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := pools.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("LB Pool still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckLBV1PoolExists(t *testing.T, n string, pool *pools.Pool) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckLBV1PoolExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := pools.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Pool not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*pool = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccLBV1Pool_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
region = "%s"
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_lb_pool_v1" "pool_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf_test_lb_pool"
|
||||||
|
protocol = "HTTP"
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
lb_method = "ROUND_ROBIN"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccLBV1Pool_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
region = "%s"
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_lb_pool_v1" "pool_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf_test_lb_pool_updated"
|
||||||
|
protocol = "HTTP"
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
lb_method = "ROUND_ROBIN"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME)
|
|
@ -0,0 +1,258 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceLBVipV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceLBVipV1Create,
|
||||||
|
Read: resourceLBVipV1Read,
|
||||||
|
Update: resourceLBVipV1Update,
|
||||||
|
Delete: resourceLBVipV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"subnet_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"protocol": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"port": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"pool_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"persistence": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"conn_limit": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"admin_state_up": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := vips.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
SubnetID: d.Get("subnet_id").(string),
|
||||||
|
Protocol: d.Get("protocol").(string),
|
||||||
|
ProtocolPort: d.Get("port").(int),
|
||||||
|
PoolID: d.Get("pool_id").(string),
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
Address: d.Get("address").(string),
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
Persistence: resourceVipPersistenceV1(d),
|
||||||
|
ConnLimit: gophercloud.MaybeInt(d.Get("conn_limit").(int)),
|
||||||
|
}
|
||||||
|
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
createOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
p, err := vips.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack LB VIP: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] LB VIP ID: %s", p.ID)
|
||||||
|
|
||||||
|
d.SetId(p.ID)
|
||||||
|
|
||||||
|
return resourceLBVipV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := vips.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "LB VIP")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived OpenStack LB VIP %s: %+v", d.Id(), p)
|
||||||
|
|
||||||
|
d.Set("name", p.Name)
|
||||||
|
d.Set("subnet_id", p.SubnetID)
|
||||||
|
d.Set("protocol", p.Protocol)
|
||||||
|
d.Set("port", p.ProtocolPort)
|
||||||
|
d.Set("pool_id", p.PoolID)
|
||||||
|
|
||||||
|
if t, exists := d.GetOk("tenant_id"); exists && t != "" {
|
||||||
|
d.Set("tenant_id", p.TenantID)
|
||||||
|
} else {
|
||||||
|
d.Set("tenant_id", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, exists := d.GetOk("address"); exists && t != "" {
|
||||||
|
d.Set("address", p.Address)
|
||||||
|
} else {
|
||||||
|
d.Set("address", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, exists := d.GetOk("description"); exists && t != "" {
|
||||||
|
d.Set("description", p.Description)
|
||||||
|
} else {
|
||||||
|
d.Set("description", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, exists := d.GetOk("persistence"); exists && t != "" {
|
||||||
|
d.Set("persistence", p.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, exists := d.GetOk("conn_limit"); exists && t != "" {
|
||||||
|
d.Set("conn_limit", p.ConnLimit)
|
||||||
|
} else {
|
||||||
|
d.Set("conn_limit", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, exists := d.GetOk("admin_state_up"); exists && t != "" {
|
||||||
|
d.Set("admin_state_up", strconv.FormatBool(p.AdminStateUp))
|
||||||
|
} else {
|
||||||
|
d.Set("admin_state_up", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateOpts vips.UpdateOpts
|
||||||
|
if d.HasChange("name") {
|
||||||
|
updateOpts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("pool_id") {
|
||||||
|
updateOpts.PoolID = d.Get("pool_id").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("description") {
|
||||||
|
updateOpts.Description = d.Get("description").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("persistence") {
|
||||||
|
updateOpts.Persistence = resourceVipPersistenceV1(d)
|
||||||
|
}
|
||||||
|
if d.HasChange("conn_limit") {
|
||||||
|
updateOpts.ConnLimit = gophercloud.MaybeInt(d.Get("conn_limit").(int))
|
||||||
|
}
|
||||||
|
if d.HasChange("admin_state_up") {
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
updateOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = vips.Update(networkingClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack LB VIP: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceLBVipV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLBVipV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vips.Delete(networkingClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack LB VIP: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceVipPersistenceV1(d *schema.ResourceData) *vips.SessionPersistence {
|
||||||
|
rawP := d.Get("persistence").(interface{})
|
||||||
|
rawMap := rawP.(map[string]interface{})
|
||||||
|
if len(rawMap) != 0 {
|
||||||
|
p := vips.SessionPersistence{}
|
||||||
|
if t, ok := rawMap["type"]; ok {
|
||||||
|
p.Type = t.(string)
|
||||||
|
}
|
||||||
|
if c, ok := rawMap["cookie_name"]; ok {
|
||||||
|
p.CookieName = c.(string)
|
||||||
|
}
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccLBV1VIP_basic(t *testing.T) {
|
||||||
|
var vip vips.VirtualIP
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckLBV1VIPDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccLBV1VIP_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckLBV1VIPExists(t, "openstack_lb_vip_v1.vip_1", &vip),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccLBV1VIP_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_lb_vip_v1.vip_1", "name", "tf_test_lb_vip_updated"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckLBV1VIPDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckLBV1VIPDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_lb_vip_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := vips.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("LB VIP still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckLBV1VIPExists(t *testing.T, n string, vip *vips.VirtualIP) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckLBV1VIPExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := vips.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("VIP not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*vip = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccLBV1VIP_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
region = "%s"
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_lb_pool_v1" "pool_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf_test_lb_pool"
|
||||||
|
protocol = "HTTP"
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
lb_method = "ROUND_ROBIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_lb_vip_v1" "vip_1" {
|
||||||
|
region = "RegionOne"
|
||||||
|
name = "tf_test_lb_vip"
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
protocol = "HTTP"
|
||||||
|
port = 80
|
||||||
|
pool_id = "${openstack_lb_pool_v1.pool_1.id}"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccLBV1VIP_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
region = "%s"
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_lb_pool_v1" "pool_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf_test_lb_pool"
|
||||||
|
protocol = "HTTP"
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
lb_method = "ROUND_ROBIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_lb_vip_v1" "vip_1" {
|
||||||
|
region = "RegionOne"
|
||||||
|
name = "tf_test_lb_vip_updated"
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
protocol = "HTTP"
|
||||||
|
port = 80
|
||||||
|
pool_id = "${openstack_lb_pool_v1.pool_1.id}"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME)
|
|
@ -0,0 +1,162 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
|
||||||
|
"github.com/rackspace/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceNetworkingFloatingIPV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceNetworkFloatingIPV2Create,
|
||||||
|
Read: resourceNetworkFloatingIPV2Read,
|
||||||
|
Delete: resourceNetworkFloatingIPV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"pool": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack network client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
poolID, err := getNetworkID(d, meta, d.Get("pool").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error retrieving floating IP pool name: %s", err)
|
||||||
|
}
|
||||||
|
if len(poolID) == 0 {
|
||||||
|
return fmt.Errorf("No network found with name: %s", d.Get("pool").(string))
|
||||||
|
}
|
||||||
|
createOpts := floatingips.CreateOpts{
|
||||||
|
FloatingNetworkID: poolID,
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
floatingIP, err := floatingips.Create(networkClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error allocating floating IP: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(floatingIP.ID)
|
||||||
|
|
||||||
|
return resourceNetworkFloatingIPV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack network client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
floatingIP, err := floatingips.Get(networkClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "floating IP")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("address", floatingIP.FloatingIP)
|
||||||
|
poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error retrieving floating IP pool name: %s", err)
|
||||||
|
}
|
||||||
|
d.Set("pool", poolName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack network client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = floatingips.Delete(networkClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting floating IP: %s", err)
|
||||||
|
}
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNetworkID(d *schema.ResourceData, meta interface{}, networkName string) (string, error) {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error creating OpenStack network client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := networks.ListOpts{Name: networkName}
|
||||||
|
pager := networks.List(networkClient, opts)
|
||||||
|
networkID := ""
|
||||||
|
|
||||||
|
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||||
|
networkList, err := networks.ExtractNetworks(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range networkList {
|
||||||
|
if n.Name == networkName {
|
||||||
|
networkID = n.ID
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return networkID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNetworkName(d *schema.ResourceData, meta interface{}, networkID string) (string, error) {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error creating OpenStack network client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := networks.ListOpts{ID: networkID}
|
||||||
|
pager := networks.List(networkClient, opts)
|
||||||
|
networkName := ""
|
||||||
|
|
||||||
|
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||||
|
networkList, err := networks.ExtractNetworks(page)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range networkList {
|
||||||
|
if n.ID == networkID {
|
||||||
|
networkName = n.Name
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return networkName, err
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccNetworkingV2FloatingIP_basic(t *testing.T) {
|
||||||
|
var floatingIP floatingips.FloatingIP
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckNetworkingV2FloatingIPDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2FloatingIP_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckNetworkingV2FloatingIPExists(t, "openstack_networking_floatingip_v2.foo", &floatingIP),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2FloatingIPDestroy) Error creating OpenStack floating IP: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_networking_floatingip_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := floatingips.Get(networkClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("FloatingIP still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floatingips.FloatingIP) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2FloatingIPExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := floatingips.Get(networkClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("FloatingIP not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*kp = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccNetworkingV2FloatingIP_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_floatingip_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
pool = "%s"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME, OS_POOL_NAME)
|
|
@ -0,0 +1,170 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceNetworkingNetworkV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceNetworkingNetworkV2Create,
|
||||||
|
Read: resourceNetworkingNetworkV2Read,
|
||||||
|
Update: resourceNetworkingNetworkV2Update,
|
||||||
|
Delete: resourceNetworkingNetworkV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"admin_state_up": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"shared": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := networks.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
createOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedRaw := d.Get("shared").(string)
|
||||||
|
if sharedRaw != "" {
|
||||||
|
shared, err := strconv.ParseBool(sharedRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("shared, if provided, must be either 'true' or 'false': %v", err)
|
||||||
|
}
|
||||||
|
createOpts.Shared = &shared
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
n, err := networks.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack Neutron network: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Network ID: %s", n.ID)
|
||||||
|
|
||||||
|
d.SetId(n.ID)
|
||||||
|
|
||||||
|
return resourceNetworkingNetworkV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := networks.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "network")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived Network %s: %+v", d.Id(), n)
|
||||||
|
|
||||||
|
d.Set("name", n.Name)
|
||||||
|
d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp))
|
||||||
|
d.Set("shared", strconv.FormatBool(n.Shared))
|
||||||
|
d.Set("tenant_id", n.TenantID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingNetworkV2Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateOpts networks.UpdateOpts
|
||||||
|
if d.HasChange("name") {
|
||||||
|
updateOpts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("admin_state_up") {
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
updateOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.HasChange("shared") {
|
||||||
|
sharedRaw := d.Get("shared").(string)
|
||||||
|
if sharedRaw != "" {
|
||||||
|
shared, err := strconv.ParseBool(sharedRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("shared, if provided, must be either 'true' or 'false': %v", err)
|
||||||
|
}
|
||||||
|
updateOpts.Shared = &shared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating Network %s with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = networks.Update(networkingClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceNetworkingNetworkV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = networks.Delete(networkingClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccNetworkingV2Network_basic(t *testing.T) {
|
||||||
|
var network networks.Network
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckNetworkingV2NetworkDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2Network_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckNetworkingV2NetworkExists(t, "openstack_networking_network_v2.foo", &network),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2Network_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_networking_network_v2.foo", "name", "network_2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2NetworkDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2NetworkDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_networking_network_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := networks.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Network still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2NetworkExists(t *testing.T, n string, network *networks.Network) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2NetworkExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := networks.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Network not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*network = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccNetworkingV2Network_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccNetworkingV2Network_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "foo" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_2"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,107 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceNetworkingRouterInterfaceV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceNetworkingRouterInterfaceV2Create,
|
||||||
|
Read: resourceNetworkingRouterInterfaceV2Read,
|
||||||
|
Delete: resourceNetworkingRouterInterfaceV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"router_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"subnet_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterInterfaceV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := routers.InterfaceOpts{
|
||||||
|
SubnetID: d.Get("subnet_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
n, err := routers.AddInterface(networkingClient, d.Get("router_id").(string), createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack Neutron router interface: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Router interface Port ID: %s", n.PortID)
|
||||||
|
|
||||||
|
d.SetId(n.PortID)
|
||||||
|
|
||||||
|
return resourceNetworkingRouterInterfaceV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterInterfaceV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := ports.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpError.Actual == 404 {
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived Router Interface %s: %+v", d.Id(), n)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterInterfaceV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOpts := routers.InterfaceOpts{
|
||||||
|
SubnetID: d.Get("subnet_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = routers.RemoveInterface(networkingClient, d.Get("router_id").(string), removeOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack Neutron Router Interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccNetworkingV2RouterInterface_basic(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckNetworkingV2RouterInterfaceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2RouterInterface_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckNetworkingV2RouterInterfaceExists(t, "openstack_networking_router_interface_v2.int_1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2RouterInterfaceDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2RouterInterfaceDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_networking_router_interface_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ports.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Router interface still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2RouterInterfaceExists(t *testing.T, n string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2RouterInterfaceExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := ports.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Router interface not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccNetworkingV2RouterInterface_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_router_v2" "router_1" {
|
||||||
|
name = "router_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_router_interface_v2" "int_1" {
|
||||||
|
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
|
||||||
|
router_id = "${openstack_networking_router_v2.router_1.id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}`)
|
|
@ -0,0 +1,169 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceNetworkingRouterV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceNetworkingRouterV2Create,
|
||||||
|
Read: resourceNetworkingRouterV2Read,
|
||||||
|
Update: resourceNetworkingRouterV2Update,
|
||||||
|
Delete: resourceNetworkingRouterV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"admin_state_up": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"external_gateway": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := routers.CreateOpts{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
createOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
|
||||||
|
externalGateway := d.Get("external_gateway").(string)
|
||||||
|
if externalGateway != "" {
|
||||||
|
gatewayInfo := routers.GatewayInfo{
|
||||||
|
NetworkID: externalGateway,
|
||||||
|
}
|
||||||
|
createOpts.GatewayInfo = &gatewayInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
n, err := routers.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack Neutron router: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Router ID: %s", n.ID)
|
||||||
|
|
||||||
|
d.SetId(n.ID)
|
||||||
|
|
||||||
|
return resourceNetworkingRouterV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := routers.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpError.Actual == 404 {
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived Router %s: %+v", d.Id(), n)
|
||||||
|
|
||||||
|
d.Set("name", n.Name)
|
||||||
|
d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp))
|
||||||
|
d.Set("tenant_id", n.TenantID)
|
||||||
|
d.Set("external_gateway", n.GatewayInfo.NetworkID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterV2Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateOpts routers.UpdateOpts
|
||||||
|
if d.HasChange("name") {
|
||||||
|
updateOpts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
if d.HasChange("admin_state_up") {
|
||||||
|
asuRaw := d.Get("admin_state_up").(string)
|
||||||
|
if asuRaw != "" {
|
||||||
|
asu, err := strconv.ParseBool(asuRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
updateOpts.AdminStateUp = &asu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating Router %s with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = routers.Update(networkingClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack Neutron Router: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceNetworkingRouterV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingRouterV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = routers.Delete(networkingClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack Neutron Router: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccNetworkingV2Router_basic(t *testing.T) {
|
||||||
|
var router routers.Router
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckNetworkingV2RouterDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2Router_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckNetworkingV2RouterExists(t, "openstack_networking_router_v2.foo", &router),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2Router_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_networking_router_v2.foo", "name", "router_2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2RouterDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2RouterDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_networking_router_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := routers.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Router still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2RouterExists(t *testing.T, n string, router *routers.Router) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2RouterExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := routers.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Router not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*router = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccNetworkingV2Router_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_router_v2" "foo" {
|
||||||
|
name = "router"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var testAccNetworkingV2Router_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_router_v2" "foo" {
|
||||||
|
name = "router_2"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}`)
|
|
@ -0,0 +1,272 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceNetworkingSubnetV2() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceNetworkingSubnetV2Create,
|
||||||
|
Read: resourceNetworkingSubnetV2Read,
|
||||||
|
Update: resourceNetworkingSubnetV2Update,
|
||||||
|
Delete: resourceNetworkingSubnetV2Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"network_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cidr": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"allocation_pools": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"start": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"end": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"gateway_ip": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"ip_version": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"enable_dhcp": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"dns_nameservers": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"host_routes": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"destination_cidr": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"next_hop": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts := subnets.CreateOpts{
|
||||||
|
NetworkID: d.Get("network_id").(string),
|
||||||
|
CIDR: d.Get("cidr").(string),
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
TenantID: d.Get("tenant_id").(string),
|
||||||
|
AllocationPools: resourceSubnetAllocationPoolsV2(d),
|
||||||
|
GatewayIP: d.Get("gateway_ip").(string),
|
||||||
|
IPVersion: d.Get("ip_version").(int),
|
||||||
|
DNSNameservers: resourceSubnetDNSNameserversV2(d),
|
||||||
|
HostRoutes: resourceSubnetHostRoutesV2(d),
|
||||||
|
}
|
||||||
|
|
||||||
|
edRaw := d.Get("enable_dhcp").(string)
|
||||||
|
if edRaw != "" {
|
||||||
|
ed, err := strconv.ParseBool(edRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("enable_dhcp, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
createOpts.EnableDHCP = &ed
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
s, err := subnets.Create(networkingClient, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Subnet ID: %s", s.ID)
|
||||||
|
|
||||||
|
d.SetId(s.ID)
|
||||||
|
|
||||||
|
return resourceNetworkingSubnetV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := subnets.Get(networkingClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return CheckDeleted(d, err, "subnet")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s)
|
||||||
|
|
||||||
|
d.Set("newtork_id", s.NetworkID)
|
||||||
|
d.Set("cidr", s.CIDR)
|
||||||
|
d.Set("ip_version", s.IPVersion)
|
||||||
|
d.Set("name", s.Name)
|
||||||
|
d.Set("tenant_id", s.TenantID)
|
||||||
|
d.Set("allocation_pools", s.AllocationPools)
|
||||||
|
d.Set("gateway_ip", s.GatewayIP)
|
||||||
|
d.Set("enable_dhcp", strconv.FormatBool(s.EnableDHCP))
|
||||||
|
d.Set("dns_nameservers", s.DNSNameservers)
|
||||||
|
d.Set("host_routes", s.HostRoutes)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateOpts subnets.UpdateOpts
|
||||||
|
|
||||||
|
if d.HasChange("name") {
|
||||||
|
updateOpts.Name = d.Get("name").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("gateway_ip") {
|
||||||
|
updateOpts.GatewayIP = d.Get("gateway_ip").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("dns_nameservers") {
|
||||||
|
updateOpts.DNSNameservers = resourceSubnetDNSNameserversV2(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("host_routes") {
|
||||||
|
updateOpts.HostRoutes = resourceSubnetHostRoutesV2(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("enable_dhcp") {
|
||||||
|
edRaw := d.Get("enable_dhcp").(string)
|
||||||
|
if edRaw != "" {
|
||||||
|
ed, err := strconv.ParseBool(edRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("enable_dhcp, if provided, must be either 'true' or 'false'")
|
||||||
|
}
|
||||||
|
updateOpts.EnableDHCP = &ed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Updating Subnet %s with options: %+v", d.Id(), updateOpts)
|
||||||
|
|
||||||
|
_, err = subnets.Update(networkingClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceNetworkingSubnetV2Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceNetworkingSubnetV2Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = subnets.Delete(networkingClient, d.Id()).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack Neutron Subnet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceSubnetAllocationPoolsV2(d *schema.ResourceData) []subnets.AllocationPool {
|
||||||
|
rawAPs := d.Get("allocation_pools").([]interface{})
|
||||||
|
aps := make([]subnets.AllocationPool, len(rawAPs))
|
||||||
|
for i, raw := range rawAPs {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
aps[i] = subnets.AllocationPool{
|
||||||
|
Start: rawMap["start"].(string),
|
||||||
|
End: rawMap["end"].(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aps
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceSubnetDNSNameserversV2(d *schema.ResourceData) []string {
|
||||||
|
rawDNSN := d.Get("dns_nameservers").(*schema.Set)
|
||||||
|
dnsn := make([]string, rawDNSN.Len())
|
||||||
|
for i, raw := range rawDNSN.List() {
|
||||||
|
dnsn[i] = raw.(string)
|
||||||
|
}
|
||||||
|
return dnsn
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceSubnetHostRoutesV2(d *schema.ResourceData) []subnets.HostRoute {
|
||||||
|
rawHR := d.Get("host_routes").([]interface{})
|
||||||
|
hr := make([]subnets.HostRoute, len(rawHR))
|
||||||
|
for i, raw := range rawHR {
|
||||||
|
rawMap := raw.(map[string]interface{})
|
||||||
|
hr[i] = subnets.HostRoute{
|
||||||
|
DestinationCIDR: rawMap["destination_cidr"].(string),
|
||||||
|
NextHop: rawMap["next_hop"].(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hr
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud/openstack/networking/v2/subnets"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccNetworkingV2Subnet_basic(t *testing.T) {
|
||||||
|
var subnet subnets.Subnet
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckNetworkingV2SubnetDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2Subnet_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckNetworkingV2SubnetExists(t, "openstack_networking_subnet_v2.subnet_1", &subnet),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccNetworkingV2Subnet_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "name", "tf-test-subnet"),
|
||||||
|
resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "gateway_ip", "192.68.0.1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2SubnetDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2SubnetDestroy) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_networking_subnet_v2" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := subnets.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Subnet still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckNetworkingV2SubnetExists(t *testing.T, n string, subnet *subnets.Subnet) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(testAccCheckNetworkingV2SubnetExists) Error creating OpenStack networking client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := subnets.Get(networkingClient, rs.Primary.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.ID != rs.Primary.ID {
|
||||||
|
return fmt.Errorf("Subnet not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*subnet = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccNetworkingV2Subnet_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
region = "%s"
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}`, OS_REGION_NAME, OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccNetworkingV2Subnet_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "network_1"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf-test-subnet"
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
gateway_ip = "192.68.0.1"
|
||||||
|
}`, OS_REGION_NAME, OS_REGION_NAME)
|
|
@ -0,0 +1,148 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceObjectStorageContainerV1() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceObjectStorageContainerV1Create,
|
||||||
|
Read: resourceObjectStorageContainerV1Read,
|
||||||
|
Update: resourceObjectStorageContainerV1Update,
|
||||||
|
Delete: resourceObjectStorageContainerV1Delete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
DefaultFunc: envDefaultFunc("OS_REGION_NAME"),
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"container_read": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"container_sync_to": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"container_sync_key": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"container_write": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"content_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
"metadata": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
objectStorageClient, err := config.objectStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack object storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := d.Get("name").(string)
|
||||||
|
|
||||||
|
createOpts := &containers.CreateOpts{
|
||||||
|
ContainerRead: d.Get("container_read").(string),
|
||||||
|
ContainerSyncTo: d.Get("container_sync_to").(string),
|
||||||
|
ContainerSyncKey: d.Get("container_sync_key").(string),
|
||||||
|
ContainerWrite: d.Get("container_write").(string),
|
||||||
|
ContentType: d.Get("content_type").(string),
|
||||||
|
Metadata: resourceContainerMetadataV2(d),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
||||||
|
_, err = containers.Create(objectStorageClient, cn, createOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack container: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Container ID: %s", cn)
|
||||||
|
|
||||||
|
// Store the ID now
|
||||||
|
d.SetId(cn)
|
||||||
|
|
||||||
|
return resourceObjectStorageContainerV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceObjectStorageContainerV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
objectStorageClient, err := config.objectStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack object storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOpts := containers.UpdateOpts{
|
||||||
|
ContainerRead: d.Get("container_read").(string),
|
||||||
|
ContainerSyncTo: d.Get("container_sync_to").(string),
|
||||||
|
ContainerSyncKey: d.Get("container_sync_key").(string),
|
||||||
|
ContainerWrite: d.Get("container_write").(string),
|
||||||
|
ContentType: d.Get("content_type").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("metadata") {
|
||||||
|
updateOpts.Metadata = resourceContainerMetadataV2(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = containers.Update(objectStorageClient, d.Id(), updateOpts).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating OpenStack container: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceObjectStorageContainerV1Read(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceObjectStorageContainerV1Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
objectStorageClient, err := config.objectStorageV1Client(d.Get("region").(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack object storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = containers.Delete(objectStorageClient, d.Id()).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting OpenStack container: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceContainerMetadataV2(d *schema.ResourceData) map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for key, val := range d.Get("metadata").(map[string]interface{}) {
|
||||||
|
m[key] = val.(string)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccObjectStorageV1Container_basic(t *testing.T) {
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckObjectStorageV1ContainerDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccObjectStorageV1Container_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_objectstorage_container_v1.container_1", "name", "tf-test-container"),
|
||||||
|
resource.TestCheckResourceAttr("openstack_objectstorage_container_v1.container_1", "content_type", "application/json"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccObjectStorageV1Container_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("openstack_objectstorage_container_v1.container_1", "content_type", "text/plain"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckObjectStorageV1ContainerDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
objectStorageClient, err := config.objectStorageV1Client(OS_REGION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating OpenStack object storage client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "openstack_objectstorage_container_v1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := containers.Get(objectStorageClient, rs.Primary.ID).Extract()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Container still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccObjectStorageV1Container_basic = fmt.Sprintf(`
|
||||||
|
resource "openstack_objectstorage_container_v1" "container_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf-test-container"
|
||||||
|
metadata {
|
||||||
|
test = "true"
|
||||||
|
}
|
||||||
|
content_type = "application/json"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
||||||
|
|
||||||
|
var testAccObjectStorageV1Container_update = fmt.Sprintf(`
|
||||||
|
resource "openstack_objectstorage_container_v1" "container_1" {
|
||||||
|
region = "%s"
|
||||||
|
name = "tf-test-container"
|
||||||
|
metadata {
|
||||||
|
test = "true"
|
||||||
|
}
|
||||||
|
content_type = "text/plain"
|
||||||
|
}`,
|
||||||
|
OS_REGION_NAME)
|
|
@ -0,0 +1,22 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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,
|
||||||
|
// sets the resource ID to the empty string instead of throwing an error.
|
||||||
|
func CheckDeleted(d *schema.ResourceData, err error, msg string) error {
|
||||||
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: %s", msg, err)
|
||||||
|
}
|
||||||
|
if errCode.Actual == 404 {
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: %s", msg, err)
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ etc. Almost any infrastructure noun can be represented as a resource in Terrafor
|
||||||
|
|
||||||
Terraform is agnostic to the underlying platforms by supporting providers. A provider
|
Terraform is agnostic to the underlying platforms by supporting providers. A provider
|
||||||
is responsible for understanding API interactions and exposing resources. Providers
|
is responsible for understanding API interactions and exposing resources. Providers
|
||||||
generally are an IaaS (e.g. AWS, DigitalOcean, GCE), PaaS (e.g. Heroku, CloudFoundry),
|
generally are an IaaS (e.g. AWS, DigitalOcean, GCE, OpenStack), PaaS (e.g. Heroku, CloudFoundry),
|
||||||
or SaaS services (e.g. Atlas, DNSimple, CloudFlare).
|
or SaaS services (e.g. Atlas, DNSimple, CloudFlare).
|
||||||
|
|
||||||
Use the navigation to the left to read about the available providers.
|
Use the navigation to the left to read about the available providers.
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "Provider: OpenStack"
|
||||||
|
sidebar_current: "docs-openstack-index"
|
||||||
|
description: |-
|
||||||
|
The OpenStack provider is used to interact with the many resources supported by OpenStack. The provider needs to be configured with the proper credentials before it can be used.
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenStack Provider
|
||||||
|
|
||||||
|
The OpenStack provider is used to interact with the
|
||||||
|
many resources supported by OpenStack. The provider needs to be configured
|
||||||
|
with the proper credentials before it can be used.
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the OpenStack Provider
|
||||||
|
provider "openstack" {
|
||||||
|
user_name = "admin"
|
||||||
|
tenant_name = "admin"
|
||||||
|
password = "pwd"
|
||||||
|
auth_url = "http://myauthurl:5000/v2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a web server
|
||||||
|
resource "openstack_compute_instance_v2" "test-server" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `auth_url` - (Required)
|
||||||
|
|
||||||
|
* `user_name` - (Optional; Required for Identity V2)
|
||||||
|
|
||||||
|
* `user_id` - (Optional)
|
||||||
|
|
||||||
|
* `password` - (Optional; Required if not using `api_key`)
|
||||||
|
|
||||||
|
* `api_key` - (Optional; Required if not using `password`)
|
||||||
|
|
||||||
|
* `domain_id` - (Optional)
|
||||||
|
|
||||||
|
* `domain_name` - (Optional)
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional)
|
||||||
|
|
||||||
|
* `tenant_name` - (Optional)
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_blockstorage_volume_v1"
|
||||||
|
sidebar_current: "docs-openstack-resource-blockstorage-volume-v1"
|
||||||
|
description: |-
|
||||||
|
Manages a V1 volume resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_blockstorage\_volume_v1
|
||||||
|
|
||||||
|
Manages a V1 volume resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_blockstorage_volume_v1" "volume_1" {
|
||||||
|
region = "RegionOne"
|
||||||
|
name = "tf-test-volume"
|
||||||
|
description = "first test volume"
|
||||||
|
size = 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to create the volume. If
|
||||||
|
omitted, the `OS_REGION_NAME` environment variable is used. Changing this
|
||||||
|
creates a new volume.
|
||||||
|
|
||||||
|
* `size` - (Required) The size of the volume to create (in gigabytes). Changing
|
||||||
|
this creates a new volume.
|
||||||
|
|
||||||
|
* `name` - (Optional) A unique name for the volume. Changing this updates the
|
||||||
|
volume's name.
|
||||||
|
|
||||||
|
* `description` - (Optional) A description of the volume. Changing this updates
|
||||||
|
the volume's description.
|
||||||
|
|
||||||
|
* `image_id` - (Optional) The image ID from which to create the volume.
|
||||||
|
Changing this creates a new volume.
|
||||||
|
|
||||||
|
* `snapshot_id` - (Optional) The snapshot ID from which to create the volume.
|
||||||
|
Changing this creates a new volume.
|
||||||
|
|
||||||
|
* `source_vol_id` - (Optional) The volume ID from which to create the volume.
|
||||||
|
Changing this creates a new volume.
|
||||||
|
|
||||||
|
* `metadata` - (Optional) Metadata key/value pairs to associate with the volume.
|
||||||
|
Changing this updates the existing volume metadata.
|
||||||
|
|
||||||
|
* `volume_type` - (Optional) The type of volume to create (either SATA or SSD).
|
||||||
|
Changing this creates a new volume.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `size` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `description` - See Argument Reference above.
|
||||||
|
* `image_id` - See Argument Reference above.
|
||||||
|
* `source_vol_id` - See Argument Reference above.
|
||||||
|
* `snapshot_id` - See Argument Reference above.
|
||||||
|
* `metadata` - See Argument Reference above.
|
||||||
|
* `volume_type` - See Argument Reference above.
|
|
@ -0,0 +1,120 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_compute_instance_v2"
|
||||||
|
sidebar_current: "docs-openstack-resource-compute-instance-v2"
|
||||||
|
description: |-
|
||||||
|
Manages a V2 VM instance resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_compute\_instance_v2
|
||||||
|
|
||||||
|
Manages a V2 VM instance resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_compute_instance_v2" "test-server" {
|
||||||
|
name = "tf-test"
|
||||||
|
image_id = "ad091b52-742f-469e-8f3c-fd81cadf0743"
|
||||||
|
flavor_id = "3"
|
||||||
|
metadata {
|
||||||
|
this = "that"
|
||||||
|
}
|
||||||
|
key_pair = "my_key_pair_name"
|
||||||
|
security_groups = ["test-group-1"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to create the server instance. If
|
||||||
|
omitted, the `OS_REGION_NAME` environment variable is used. Changing this
|
||||||
|
creates a new server.
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource.
|
||||||
|
|
||||||
|
* `image_id` - (Optional; Required if `image_name` is empty) The image ID of
|
||||||
|
the desired image for the server. Changing this creates a new server.
|
||||||
|
|
||||||
|
* `image_name` - (Optional; Required if `image_id` is empty) The name of the
|
||||||
|
desired image for the server. Changing this creates a new server.
|
||||||
|
|
||||||
|
* `flavor_id` - (Optional; Required if `flavor_name` is empty) The flavor ID of
|
||||||
|
the desired flavor for the server. Changing this resizes the existing server.
|
||||||
|
|
||||||
|
* `flavor_name` - (Optional; Required if `flavor_id` is empty) The name of the
|
||||||
|
desired flavor for the server. Changing this resizes the existing server.
|
||||||
|
|
||||||
|
* `security_groups` - (Optional) An array of one or more security group names
|
||||||
|
to associate with the server. Changing this results in adding/removing
|
||||||
|
security groups from the existing server.
|
||||||
|
|
||||||
|
* `availability_zone` - (Optional) The availability zone in which to create
|
||||||
|
the server. Changing this creates a new server.
|
||||||
|
|
||||||
|
* `network` - (Optional) An array of one or more networks to attach to the
|
||||||
|
instance. The network object structure is documented below. Changing this
|
||||||
|
creates a new server.
|
||||||
|
|
||||||
|
* `metadata` - (Optional) Metadata key/value pairs to make available from
|
||||||
|
within the instance. Changing this updates the existing server metadata.
|
||||||
|
|
||||||
|
* `admin_pass` - (Optional) The administrative password to assign to the server.
|
||||||
|
Changing this changes the root password on the existing server.
|
||||||
|
|
||||||
|
* `key_pair` - (Optional) The name of a key pair to put on the server. The key
|
||||||
|
pair must already be created and associated with the tenant's account.
|
||||||
|
Changing this creates a new server.
|
||||||
|
|
||||||
|
* `block_device` - (Optional) The object for booting by volume. The block_device
|
||||||
|
object structure is documented below. Changing this creates a new server.
|
||||||
|
|
||||||
|
* `volume` - (Optional) Attach an existing volume to the instance. The volume
|
||||||
|
structure is described below.
|
||||||
|
|
||||||
|
The `network` block supports:
|
||||||
|
|
||||||
|
* `uuid` - (Required unless `port` is provided) The network UUID to attach to
|
||||||
|
the server.
|
||||||
|
|
||||||
|
* `port` - (Required unless `uuid` is provided) The port UUID of a network to
|
||||||
|
attach to the server.
|
||||||
|
|
||||||
|
* `fixed_ip` - (Optional) Specifies a fixed IP address to be used on this
|
||||||
|
network.
|
||||||
|
|
||||||
|
The `block_device` block supports:
|
||||||
|
|
||||||
|
* `uuid` - (Required) The UUID of the image, volume, or snapshot.
|
||||||
|
|
||||||
|
* `source_type` - (Required) The source type of the device. Must be one of
|
||||||
|
"image", "volume", or "snapshot".
|
||||||
|
|
||||||
|
* `volume_size` - (Optional) The size of the volume to create (in gigabytes).
|
||||||
|
|
||||||
|
* `boot_index` - (Optional) The boot index of the volume. It defaults to 0.
|
||||||
|
|
||||||
|
* `destination_type` - (Optional) The type that gets created. Possible values
|
||||||
|
are "volume" and "local".
|
||||||
|
|
||||||
|
The `volume` block supports:
|
||||||
|
|
||||||
|
* `volume_id` - (Required) The UUID of the volume to attach.
|
||||||
|
|
||||||
|
* `device` - (Optional) The device that the volume will be attached as. For
|
||||||
|
example: `/dev/vdc`. Omit this option to allow the volume to be
|
||||||
|
auto-assigned a device.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `access_ip_v4` - See Argument Reference above.
|
||||||
|
* `access_ip_v6` - See Argument Reference above.
|
||||||
|
* `metadata` - See Argument Reference above.
|
||||||
|
* `security_groups` - See Argument Reference above.
|
||||||
|
* `flavor_ref` - See Argument Reference above.
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_compute_keypair_v2"
|
||||||
|
sidebar_current: "docs-openstack-resource-compute-keypair-v2"
|
||||||
|
description: |-
|
||||||
|
Manages a V2 keypair resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_compute\_keypair_v2
|
||||||
|
|
||||||
|
Manages a V2 keypair resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_compute_keypair_v2" "test-keypair" {
|
||||||
|
name = "my-keypair"
|
||||||
|
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAjpC1hwiOCCmKEWxJ4qzTTsJbKzndLotBCz5PcwtUnflmU+gHJtWMZKpuEGVi29h0A/+ydKek1O18k10Ff+4tyFjiHDQAnOfgWf7+b1yK+qDip3X1C0UPMbwHlTfSGWLGZqd9LvEFx9k3h/M+VtMvwR1lJ9LUyTAImnNjWG7TaIPmui30HvM2UiFEmqkr4ijq45MyX2+fLIePLRIF61p4whjHAQYufqyno3BS48icQb4p6iVEZPo4AE2o9oIyQvj2mx4dk5Y8CgSETOZTYDOR3rU2fZTRDRgPJDH9FWvQjF5tA0p3d9CoWWd2s6GKKbfoUIi8R/Db1BSPJwkqB"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Compute client.
|
||||||
|
Keypairs are associated with accounts, but a Compute client is needed to
|
||||||
|
create one. If omitted, the `OS_REGION_NAME` environment variable is used.
|
||||||
|
Changing this creates a new keypair.
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the keypair. Changing this creates a new
|
||||||
|
keypair.
|
||||||
|
|
||||||
|
* `public_key` - (Required) A pregenerated OpenSSH-formatted public key.
|
||||||
|
Changing this creates a new keypair.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `public_key` - See Argument Reference above.
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_compute_secgroup_v2"
|
||||||
|
sidebar_current: "docs-openstack-resource-compute-secgroup-2"
|
||||||
|
description: |-
|
||||||
|
Manages a V2 security group resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_compute\_secgroup_v2
|
||||||
|
|
||||||
|
Manages a V2 security group resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_compute_secgroup_v2" "secgroup_1" {
|
||||||
|
name = "my_secgroup"
|
||||||
|
description = "my security group"
|
||||||
|
rule {
|
||||||
|
from_port = 22
|
||||||
|
to_port = 22
|
||||||
|
ip_protocol = "tcp"
|
||||||
|
cidr = "0.0.0.0/0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Compute client.
|
||||||
|
A Compute client is needed to create a security group. If omitted, the
|
||||||
|
`OS_REGION_NAME` environment variable is used. Changing this creates a new
|
||||||
|
security group.
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the security group. Changing this
|
||||||
|
updates the `name` of an existing security group.
|
||||||
|
|
||||||
|
* `description` - (Required) A description for the security group. Changing this
|
||||||
|
updates the `description` of an existing security group.
|
||||||
|
|
||||||
|
* `rule` - (Optional) A rule describing how the security group operates. The
|
||||||
|
rule object structure is documented below. Changing this updates the
|
||||||
|
security group rules.
|
||||||
|
|
||||||
|
The `rule` block supports:
|
||||||
|
|
||||||
|
* `from_port` - (Required) An integer representing the lower bound of the port
|
||||||
|
range to open. Changing this creates a new security group rule.
|
||||||
|
|
||||||
|
* `to_port` - (Required) An integer representing the upper bound of the port
|
||||||
|
range to open. Changing this creates a new security group rule.
|
||||||
|
|
||||||
|
* `ip_protocol` - (Required) The protocol type that will be allowed. Changing
|
||||||
|
this creates a new security group rule.
|
||||||
|
|
||||||
|
* `cidr` - (Optional) Required if `from_group_id` is empty. The IP range that
|
||||||
|
will be the source of network traffic to the security group. Use 0.0.0.0./0
|
||||||
|
to allow all IP addresses. Changing this creates a new security group rule.
|
||||||
|
|
||||||
|
* `from_group_id` - (Optional) Required if `cidr` is empty. The ID of a group
|
||||||
|
from which to forward traffic to the parent group. Changing
|
||||||
|
this creates a new security group rule.
|
||||||
|
|
||||||
|
* `self` - (Optional) Required if `cidr` and `from_group_id` is empty. If true,
|
||||||
|
the security group itself will be added as a source to this ingress rule.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `description` - See Argument Reference above.
|
||||||
|
* `rule` - See Argument Reference above.
|
|
@ -0,0 +1,82 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_lb_monitor_v1"
|
||||||
|
sidebar_current: "docs-openstack-resource-lb-monitor-v1"
|
||||||
|
description: |-
|
||||||
|
Manages a V1 load balancer monitor resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_lb\_monitor_v1
|
||||||
|
|
||||||
|
Manages a V1 load balancer monitor resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_lb_monitor_v1" "monitor_1" {
|
||||||
|
type = "PING"
|
||||||
|
delay = 30
|
||||||
|
timeout = 5
|
||||||
|
max_retries = 3
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Networking client.
|
||||||
|
A Networking client is needed to create an LB monitor. If omitted, the
|
||||||
|
`OS_REGION_NAME` environment variable is used. Changing this creates a new
|
||||||
|
LB monitor.
|
||||||
|
|
||||||
|
* `type` - (Required) The type of probe, which is PING, TCP, HTTP, or HTTPS,
|
||||||
|
that is sent by the monitor to verify the member state. Changing this
|
||||||
|
creates a new monitor.
|
||||||
|
|
||||||
|
* `delay` - (Required) The time, in seconds, between sending probes to members.
|
||||||
|
Changing this creates a new monitor.
|
||||||
|
|
||||||
|
* `timeout` - (Required) Maximum number of seconds for a monitor to wait for a
|
||||||
|
ping reply before it times out. The value must be less than the delay value.
|
||||||
|
Changing this updates the timeout of the existing monitor.
|
||||||
|
|
||||||
|
* `max_retries` - (Required) Number of permissible ping failures before changing
|
||||||
|
the member's status to INACTIVE. Must be a number between 1 and 10. Changing
|
||||||
|
this updates the max_retries of the existing monitor.
|
||||||
|
|
||||||
|
* `url_path` - (Optional) Required for HTTP(S) types. URI path that will be
|
||||||
|
accessed if monitor type is HTTP or HTTPS. Changing this updates the
|
||||||
|
url_path of the existing monitor.
|
||||||
|
|
||||||
|
* `http_method` - (Optional) Required for HTTP(S) types. The HTTP method used
|
||||||
|
for requests by the monitor. If this attribute is not specified, it defaults
|
||||||
|
to "GET". Changing this updates the http_method of the existing monitor.
|
||||||
|
|
||||||
|
* `expected_codes` - (Optional) equired for HTTP(S) types. Expected HTTP codes
|
||||||
|
for a passing HTTP(S) monitor. You can either specify a single status like
|
||||||
|
"200", or a range like "200-202". Changing this updates the expected_codes
|
||||||
|
of the existing monitor.
|
||||||
|
|
||||||
|
* `admin_state_up` - (Optional) The administrative state of the monitor.
|
||||||
|
Acceptable values are "true" and "false". Changing this value updates the
|
||||||
|
state of the existing monitor.
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional) The owner of the monitor. Required if admin wants to
|
||||||
|
create a monitor for another tenant. Changing this creates a new monitor.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `type` - See Argument Reference above.
|
||||||
|
* `delay` - See Argument Reference above.
|
||||||
|
* `timeout` - See Argument Reference above.
|
||||||
|
* `max_retries` - See Argument Reference above.
|
||||||
|
* `url_path` - See Argument Reference above.
|
||||||
|
* `http_method` - See Argument Reference above.
|
||||||
|
* `expected_codes` - See Argument Reference above.
|
||||||
|
* `admin_state_up` - See Argument Reference above.
|
||||||
|
* `tenant_id` - See Argument Reference above.
|
|
@ -0,0 +1,90 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_lb_pool_v1"
|
||||||
|
sidebar_current: "docs-openstack-resource-lb-pool-v1"
|
||||||
|
description: |-
|
||||||
|
Manages a V1 load balancer pool resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_lb\_pool_v1
|
||||||
|
|
||||||
|
Manages a V1 load balancer pool resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_lb_pool_v1" "pool_1" {
|
||||||
|
name = "tf_test_lb_pool"
|
||||||
|
protocol = "HTTP"
|
||||||
|
subnet_id = "12345"
|
||||||
|
lb_method = "ROUND_ROBIN"
|
||||||
|
monitor_ids = ["67890"]
|
||||||
|
member {
|
||||||
|
address = "192.168.0.1"
|
||||||
|
port = 80
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Networking client.
|
||||||
|
A Networking client is needed to create an LB pool. If omitted, the
|
||||||
|
`OS_REGION_NAME` environment variable is used. Changing this creates a new
|
||||||
|
LB pool.
|
||||||
|
|
||||||
|
* `name` - (Required) The name of the pool. Changing this updates the name of
|
||||||
|
the existing pool.
|
||||||
|
|
||||||
|
* `protocol` - (Required) The protocol used by the pool members, you can use
|
||||||
|
either 'TCP, 'HTTP', or 'HTTPS'. Changing this creates a new pool.
|
||||||
|
|
||||||
|
* `subnet_id` - (Required) The network on which the members of the pool will be
|
||||||
|
located. Only members that are on this network can be added to the pool.
|
||||||
|
Changing this creates a new pool.
|
||||||
|
|
||||||
|
* `lb_method` - (Required) The algorithm used to distribute load between the
|
||||||
|
members of the pool. The current specification supports 'ROUND_ROBIN' and
|
||||||
|
'LEAST_CONNECTIONS' as valid values for this attribute.
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional) The owner of the pool. Required if admin wants to
|
||||||
|
create a pool member for another tenant. Changing this creates a new pool.
|
||||||
|
|
||||||
|
* `monitor_ids` - (Optional) A list of IDs of monitors to associate with the
|
||||||
|
pool.
|
||||||
|
|
||||||
|
* `member` - (Optional) An existing node to add to the pool. Changing this
|
||||||
|
updates the members of the pool. The member object structure is documented
|
||||||
|
below.
|
||||||
|
|
||||||
|
The `member` block supports:
|
||||||
|
|
||||||
|
* `address` - (Required) The IP address of the member. Changing this creates a
|
||||||
|
new member.
|
||||||
|
|
||||||
|
* `port` - (Required) An integer representing the port on which the member is
|
||||||
|
hosted. Changing this creates a new member.
|
||||||
|
|
||||||
|
* `admin_state_up` - (Optional) The administrative state of the member.
|
||||||
|
Acceptable values are 'true' and 'false'. Changing this value updates the
|
||||||
|
state of the existing member.
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional) The owner of the member. Required if admin wants to
|
||||||
|
create a pool member for another tenant. Changing this creates a new member.
|
||||||
|
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `protocol` - See Argument Reference above.
|
||||||
|
* `subnet_id` - See Argument Reference above.
|
||||||
|
* `lb_method` - See Argument Reference above.
|
||||||
|
* `tenant_id` - See Argument Reference above.
|
||||||
|
* `monitor_id` - See Argument Reference above.
|
||||||
|
* `member` - See Argument Reference above.
|
|
@ -0,0 +1,95 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_lb_vip_v1"
|
||||||
|
sidebar_current: "docs-openstack-resource-lb-vip-v1"
|
||||||
|
description: |-
|
||||||
|
Manages a V1 load balancer vip resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_lb\_vip_v1
|
||||||
|
|
||||||
|
Manages a V1 load balancer vip resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_lb_vip_v1" "vip_1" {
|
||||||
|
name = "tf_test_lb_vip"
|
||||||
|
subnet_id = "12345"
|
||||||
|
protocol = "HTTP"
|
||||||
|
port = 80
|
||||||
|
pool_id = "67890"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Networking client.
|
||||||
|
A Networking client is needed to create a VIP. If omitted, the
|
||||||
|
`OS_REGION_NAME` environment variable is used. Changing this creates a new
|
||||||
|
VIP.
|
||||||
|
|
||||||
|
* `name` - (Required) The name of the vip. Changing this updates the name of
|
||||||
|
the existing vip.
|
||||||
|
|
||||||
|
* `subnet_id` - (Required) The network on which to allocate the vip's address. A
|
||||||
|
tenant can only create vips on networks authorized by policy (e.g. networks
|
||||||
|
that belong to them or networks that are shared). Changing this creates a
|
||||||
|
new vip.
|
||||||
|
|
||||||
|
* `protocol` - (Required) The protocol - can be either 'TCP, 'HTTP', or
|
||||||
|
HTTPS'. Changing this creates a new vip.
|
||||||
|
|
||||||
|
* `port` - (Required) The port on which to listen for client traffic. Changing
|
||||||
|
this creates a new vip.
|
||||||
|
|
||||||
|
* `pool_id` - (Required) The ID of the pool with which the vip is associated.
|
||||||
|
Changing this updates the pool_id of the existing vip.
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional) The owner of the vip. Required if admin wants to
|
||||||
|
create a vip member for another tenant. Changing this creates a new vip.
|
||||||
|
|
||||||
|
* `address` - (Optional) The IP address of the vip. Changing this creates a new
|
||||||
|
vip.
|
||||||
|
|
||||||
|
* `description` - (Optional) Human-readable description for the vip. Changing
|
||||||
|
this updates the description of the existing vip.
|
||||||
|
|
||||||
|
* `persistence` - (Optional) Omit this field to prevent session persistence.
|
||||||
|
The persistence object structure is documented below. Changing this updates
|
||||||
|
the persistence of the existing vip.
|
||||||
|
|
||||||
|
* `conn_limit` - (Optional) The maximum number of connections allowed for the
|
||||||
|
vip. Default is -1, meaning no limit. Changing this updates the conn_limit
|
||||||
|
of the existing vip.
|
||||||
|
|
||||||
|
* `admin_state_up` - (Optional) The administrative state of the vip.
|
||||||
|
Acceptable values are "true" and "false". Changing this value updates the
|
||||||
|
state of the existing vip.
|
||||||
|
|
||||||
|
The `persistence` block supports:
|
||||||
|
|
||||||
|
* `type` - (Required) The type of persistence mode. Valid values are "SOURCE_IP",
|
||||||
|
"HTTP_COOKIE", or "APP_COOKIE".
|
||||||
|
|
||||||
|
* `cookie_name` - (Optional) The name of the cookie if persistence mode is set
|
||||||
|
appropriately.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `subnet_id` - See Argument Reference above.
|
||||||
|
* `protocol` - See Argument Reference above.
|
||||||
|
* `port` - See Argument Reference above.
|
||||||
|
* `pool_id` - See Argument Reference above.
|
||||||
|
* `tenant_id` - See Argument Reference above.
|
||||||
|
* `address` - See Argument Reference above.
|
||||||
|
* `description` - See Argument Reference above.
|
||||||
|
* `persistence` - See Argument Reference above.
|
||||||
|
* `conn_limit` - See Argument Reference above.
|
||||||
|
* `admin_state_up` - See Argument Reference above.
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_networking_network_v2"
|
||||||
|
sidebar_current: "docs-openstack-resource-networking-network-v2"
|
||||||
|
description: |-
|
||||||
|
Manages a V2 Neutron network resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_networking\_network_v2
|
||||||
|
|
||||||
|
Manages a V2 Neutron network resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
name = "tf_test_network"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Networking client.
|
||||||
|
A Networking client is needed to create a Neutron network. If omitted, the
|
||||||
|
`OS_REGION_NAME` environment variable is used. Changing this creates a new
|
||||||
|
network.
|
||||||
|
|
||||||
|
* `name` - (Optional) The name of the network. Changing this updates the name of
|
||||||
|
the existing network.
|
||||||
|
|
||||||
|
* `shared` - (Optional) Specifies whether the network resource can be accessed
|
||||||
|
by any tenant or not. Changing this updates the sharing capabalities of the
|
||||||
|
existing network.
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional) The owner of the newtork. Required if admin wants to
|
||||||
|
create a network for another tenant. Changing this creates a new network.
|
||||||
|
|
||||||
|
* `admin_state_up` - (Optional) The administrative state of the network.
|
||||||
|
Acceptable values are "true" and "false". Changing this value updates the
|
||||||
|
state of the existing network.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `shared` - See Argument Reference above.
|
||||||
|
* `tenant_id` - See Argument Reference above.
|
||||||
|
* `admin_state_up` - See Argument Reference above.
|
|
@ -0,0 +1,98 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_networking_subnet_v2"
|
||||||
|
sidebar_current: "docs-openstack-resource-networking-subnet-v2"
|
||||||
|
description: |-
|
||||||
|
Manages a V2 Neutron subnet resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_networking\_subnet_v2
|
||||||
|
|
||||||
|
Manages a V2 Neutron subnet resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_networking_network_v2" "network_1" {
|
||||||
|
name = "tf_test_network"
|
||||||
|
admin_state_up = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "openstack_networking_subnet_v2" "subnet_1" {
|
||||||
|
network_id = "${openstack_networking_network_v2.network_1.id}"
|
||||||
|
cidr = "192.168.199.0/24"
|
||||||
|
ip_version = 4
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to obtain the V2 Networking client.
|
||||||
|
A Networking client is needed to create a Neutron subnet. If omitted, the
|
||||||
|
`OS_REGION_NAME` environment variable is used. Changing this creates a new
|
||||||
|
subnet.
|
||||||
|
|
||||||
|
* `network_id` - (Required) The UUID of the parent network. Changing this
|
||||||
|
creates a new subnet.
|
||||||
|
|
||||||
|
* `cidr` - (Required) CIDR representing IP range for this subnet, based on IP
|
||||||
|
version. Changing this creates a new subnet.
|
||||||
|
|
||||||
|
* `ip_version` - (Required) IP version, either 4 or 6. Changing this creates a
|
||||||
|
new subnet.
|
||||||
|
|
||||||
|
* `name` - (Optional) The name of the subnet. Changing this updates the name of
|
||||||
|
the existing subnet.
|
||||||
|
|
||||||
|
* `tenant_id` - (Optional) The owner of the subnet. Required if admin wants to
|
||||||
|
create a subnet for another tenant. Changing this creates a new subnet.
|
||||||
|
|
||||||
|
* `allocation_pools` - (Optional) An array of sub-ranges of CIDR available for
|
||||||
|
dynamic allocation to ports. The allocation_pool object structure is
|
||||||
|
documented below. Changing this creates a new subnet.
|
||||||
|
|
||||||
|
* `gateway_ip` - (Optional) Default gateway used by devices in this subnet.
|
||||||
|
Changing this updates the gateway IP of the existing subnet.
|
||||||
|
|
||||||
|
* `enable_dhcp` - (Optional) The administrative state of the network.
|
||||||
|
Acceptable values are "true" and "false". Changing this value enables or
|
||||||
|
disables the DHCP capabilities of the existing subnet.
|
||||||
|
|
||||||
|
* `dns_nameservers` - (Optional) An array of DNS name server names used by hosts
|
||||||
|
in this subnet. Changing this updates the DNS name servers for the existing
|
||||||
|
subnet.
|
||||||
|
|
||||||
|
* `host_routes` - (Optional) An array of routes that should be used by devices
|
||||||
|
with IPs from this subnet (not including local subnet route). The host_route
|
||||||
|
object structure is documented below. Changing this updates the host routes
|
||||||
|
for the existing subnet.
|
||||||
|
|
||||||
|
The `allocation_pools` block supports:
|
||||||
|
|
||||||
|
* `start` - (Required) The starting address.
|
||||||
|
|
||||||
|
* `end` - (Required) The ending address.
|
||||||
|
|
||||||
|
The `host_routes` block supports:
|
||||||
|
|
||||||
|
* `destination_cidr` - (Required) The destination CIDR.
|
||||||
|
|
||||||
|
* `next_hop` - (Required) The next hop in the route.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `network_id` - See Argument Reference above.
|
||||||
|
* `cidr` - See Argument Reference above.
|
||||||
|
* `ip_version` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `tenant_id` - See Argument Reference above.
|
||||||
|
* `allocation_pools` - See Argument Reference above.
|
||||||
|
* `gateway_ip` - See Argument Reference above.
|
||||||
|
* `enable_dhcp` - See Argument Reference above.
|
||||||
|
* `dns_nameservers` - See Argument Reference above.
|
||||||
|
* `host_routes` - See Argument Reference above.
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
layout: "openstack"
|
||||||
|
page_title: "OpenStack: openstack_objectstorage_container_v1"
|
||||||
|
sidebar_current: "docs-openstack-resource-objectstorage-container-v1"
|
||||||
|
description: |-
|
||||||
|
Manages a V1 container resource within OpenStack.
|
||||||
|
---
|
||||||
|
|
||||||
|
# openstack\_objectstorage\_container_v1
|
||||||
|
|
||||||
|
Manages a V1 container resource within OpenStack.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "openstack_objectstorage_container_v1" "container_1" {
|
||||||
|
region = "RegionOne"
|
||||||
|
name = "tf-test-container-1"
|
||||||
|
metadata {
|
||||||
|
test = "true"
|
||||||
|
}
|
||||||
|
content_type = "application/json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `region` - (Required) The region in which to create the container. If
|
||||||
|
omitted, the `OS_REGION_NAME` environment variable is used. Changing this
|
||||||
|
creates a new container.
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the container. Changing this creates a
|
||||||
|
new container.
|
||||||
|
|
||||||
|
* `container_read` - (Optional) Sets an access control list (ACL) that grants
|
||||||
|
read access. This header can contain a comma-delimited list of users that
|
||||||
|
can read the container (allows the GET method for all objects in the
|
||||||
|
container). Changing this updates the access control list read access.
|
||||||
|
|
||||||
|
* `container_sync_to` - (Optional) The destination for container synchronization.
|
||||||
|
Changing this updates container synchronization.
|
||||||
|
|
||||||
|
* `container_sync_key` - (Optional) The secret key for container synchronization.
|
||||||
|
Changing this updates container synchronization.
|
||||||
|
|
||||||
|
* `container_write` - (Optional) Sets an ACL that grants write access.
|
||||||
|
Changing this updates the access control list write access.
|
||||||
|
|
||||||
|
* `metadata` - (Optional) Custom key/value pairs to associate with the container.
|
||||||
|
Changing this updates the existing container metadata.
|
||||||
|
|
||||||
|
* `content_type` - (Optional) The MIME type for the container. Changing this
|
||||||
|
updates the MIME type.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `region` - See Argument Reference above.
|
||||||
|
* `name` - See Argument Reference above.
|
||||||
|
* `container_read` - See Argument Reference above.
|
||||||
|
* `container_sync_to` - See Argument Reference above.
|
||||||
|
* `container_sync_key` - See Argument Reference above.
|
||||||
|
* `container_write` - See Argument Reference above.
|
||||||
|
* `metadata` - See Argument Reference above.
|
||||||
|
* `content_type` - See Argument Reference above.
|
|
@ -0,0 +1,53 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-openstack-index") %>>
|
||||||
|
<a href="/docs/providers/openstack/index.html">OpenStack Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource") %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-blockstorage-volume-v1") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/blockstorage_volume_v1.html">openstack_blockstorage_volume_v1</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-compute-instance-v2") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/compute_instance_v2.html">openstack_compute_instance_v2</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-compute-keypair-v2") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/compute_keypair_v2.html">openstack_compute_keypair_v2</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-compute-secgroup-v2") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/compute_secgroup_v2.html">openstack_compute_secgroup_v2</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-lb-monitor-v1") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/lb_monitor_v1.html">openstack_lb_monitor_v1</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-lb-pool-v1") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/lb_pool_v1.html">openstack_lb_pool_v1</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-lb-vip-v1") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/lb_vip_v1.html">openstack_lb_vip_v1</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-networking-network-v2") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/networking_network_v2.html">openstack_networking_network_v2</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-networking-subnet-v2") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/networking_subnet_v2.html">openstack_networking_subnet_v2</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-openstack-resource-objectstorage-container-v1") %>>
|
||||||
|
<a href="/docs/providers/openstack/r/objectstorage_container_v1.html">openstack_objectstorage_container_v1</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
Loading…
Reference in New Issue