Merge pull request #6004 from svanharmelen/f-cloudstack-static-nat

provider/cloudstack: add `cloudstack_static_nat` resource
This commit is contained in:
Sander van Harmelen 2016-04-04 18:17:57 +02:00
commit f81334fd88
5 changed files with 336 additions and 1 deletions

View File

@ -54,6 +54,7 @@ func Provider() terraform.ResourceProvider {
"cloudstack_port_forward": resourceCloudStackPortForward(), "cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(), "cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(),
"cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(), "cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(),
"cloudstack_static_nat": resourceCloudStackStaticNAT(),
"cloudstack_template": resourceCloudStackTemplate(), "cloudstack_template": resourceCloudStackTemplate(),
"cloudstack_vpc": resourceCloudStackVPC(), "cloudstack_vpc": resourceCloudStackVPC(),
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(), "cloudstack_vpn_connection": resourceCloudStackVPNConnection(),

View File

@ -99,7 +99,7 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{})
func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) error { func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient) cs := meta.(*cloudstack.CloudStackClient)
// Get the network ACL list details // Get the IP address details
f, count, err := cs.Address.GetPublicIpAddressByID(d.Id()) f, count, err := cs.Address.GetPublicIpAddressByID(d.Id())
if err != nil { if err != nil {
if count == 0 { if count == 0 {

View File

@ -0,0 +1,156 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackStaticNAT() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackStaticNATCreate,
Exists: resourceCloudStackStaticNATExists,
Read: resourceCloudStackStaticNATRead,
Delete: resourceCloudStackStaticNATDelete,
Schema: map[string]*schema.Schema{
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"virtual_machine": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"vm_guest_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackStaticNATCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the ipaddress ID
ipaddressid, e := retrieveID(cs, "ipaddress", d.Get("ipaddress").(string))
if e != nil {
return e.Error()
}
// Retrieve the virtual_machine ID
virtualmachineid, e := retrieveID(cs, "virtual_machine", d.Get("virtual_machine").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.NAT.NewEnableStaticNatParams(ipaddressid, virtualmachineid)
if network, ok := d.GetOk("network"); ok {
// Retrieve the network ID
networkid, e := retrieveID(cs, "network", network.(string))
if e != nil {
return e.Error()
}
p.SetNetworkid(networkid)
}
if vmGuestIP, ok := d.GetOk("vm_guest_ip"); ok {
p.SetVmguestip(vmGuestIP.(string))
}
_, err := cs.NAT.EnableStaticNat(p)
if err != nil {
return fmt.Errorf("Error enabling static NAT: %s", err)
}
d.SetId(ipaddressid)
return resourceCloudStackStaticNATRead(d, meta)
}
func resourceCloudStackStaticNATExists(d *schema.ResourceData, meta interface{}) (bool, error) {
cs := meta.(*cloudstack.CloudStackClient)
// Get the IP address details
ip, count, err := cs.Address.GetPublicIpAddressByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] IP address with ID %s no longer exists", d.Id())
return false, nil
}
return false, err
}
return ip.Isstaticnat, nil
}
func resourceCloudStackStaticNATRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the IP address details
ip, count, err := cs.Address.GetPublicIpAddressByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] IP address with ID %s no longer exists", d.Id())
d.SetId("")
return nil
}
return err
}
if !ip.Isstaticnat {
log.Printf("[DEBUG] Static NAT is no longer enabled for IP address with ID %s", d.Id())
d.SetId("")
return nil
}
setValueOrID(d, "network", ip.Associatednetworkname, ip.Associatednetworkid)
setValueOrID(d, "virtual_machine", ip.Virtualmachinename, ip.Virtualmachineid)
d.Set("vm_guest_ip", ip.Vmipaddress)
return nil
}
func resourceCloudStackStaticNATDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.NAT.NewDisableStaticNatParams(d.Id())
// Disable static NAT
_, err := cs.NAT.DisableStaticNat(p)
if err != nil {
// This is a very poor way to be told the ID does no longer exist :(
if strings.Contains(err.Error(), fmt.Sprintf(
"Invalid parameter id value=%s due to incorrect long value format, "+
"or entity does not exist", d.Id())) {
return nil
}
return fmt.Errorf("Error disabling static NAT: %s", err)
}
return nil
}

View File

@ -0,0 +1,128 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackStaticNAT_basic(t *testing.T) {
var ipaddr cloudstack.PublicIpAddress
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackStaticNATDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackStaticNAT_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackStaticNATExists(
"cloudstack_static_nat.foo", &ipaddr),
testAccCheckCloudStackStaticNATAttributes(&ipaddr),
),
},
},
})
}
func testAccCheckCloudStackStaticNATExists(
n string, ipaddr *cloudstack.PublicIpAddress) 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 static NAT ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
ip, _, err := cs.Address.GetPublicIpAddressByID(rs.Primary.ID)
if err != nil {
return err
}
if ip.Id != rs.Primary.ID {
return fmt.Errorf("Static NAT not found")
}
if !ip.Isstaticnat {
return fmt.Errorf("Static NAT not enabled")
}
*ipaddr = *ip
return nil
}
}
func testAccCheckCloudStackStaticNATAttributes(
ipaddr *cloudstack.PublicIpAddress) resource.TestCheckFunc {
return func(s *terraform.State) error {
if ipaddr.Associatednetworkname != CLOUDSTACK_NETWORK_1 {
return fmt.Errorf("Bad network: %s", ipaddr.Associatednetworkname)
}
if ipaddr.Virtualmachinename != "terraform-test" {
return fmt.Errorf("Bad virtual_machine: %s", ipaddr.Virtualmachinename)
}
return nil
}
}
func testAccCheckCloudStackStaticNATDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_static_nat" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No static NAT ID is set")
}
ip, _, err := cs.Address.GetPublicIpAddressByID(rs.Primary.ID)
if err == nil && ip.Isstaticnat {
return fmt.Errorf("Static NAT %s still enabled", rs.Primary.ID)
}
}
return nil
}
var testAccCloudStackStaticNAT_basic = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform-test"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}
resource "cloudstack_ipaddress" "foo" {
network = "%s"
}
resource "cloudstack_static_nat" "foo" {
ipaddress = "${cloudstack_ipaddress.foo.id}"
network = "${cloudstack_ipaddress.foo.network}"
virtual_machine = "${cloudstack_instance.foobar.id}"
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_NETWORK_1,
)

View File

@ -0,0 +1,50 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_static_nat"
sidebar_current: "docs-cloudstack-resource-static-nat"
description: |-
Enables static NAT for a given IP address.
---
# cloudstack\_static\_nat
Enables static NAT for a given IP address
## Example Usage
```
resource "cloudstack_static_nat" "default" {
ipaddress = "192.168.0.1"
virtual_machine = "server-1"
}
```
## Argument Reference
The following arguments are supported:
* `ipaddress` - (Required) The name or ID of the public IP address for which
static NAT will be enabled. Changing this forces a new resource to be
created.
* `network` - (Optional) The name or ID of the network of the VM the static
NAT will be enabled for. Required when public IP address is not
associated with any guest network yet (VPC case). Changing this forces
a new resource to be created.
* `virtual_machine` - (Required) The name or ID of the virtual machine to
enable the static NAT feature for. Changing this forces a new resource
to be created.
* `vm_guest_ip` - (Optional) The virtual machine IP address for the port
forwarding rule (useful when the virtual machine has a secondairy NIC).
Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The static nat ID.
* `network` - The network the public IP address is associated with.
* `vm_guest_ip` - The IP address of the virtual machine that is used
for the port forwarding rule.