Adding private gateway and static route resource to cloudstack provider (#9637)

* Adding private gateway and static route resource to cloudstack provider

Testing the private gateway and static route resource requires a ROOT
account in Cloudstack

* changes requested by reviewer
This commit is contained in:
ddegoede 2016-10-27 21:06:39 +02:00 committed by Sander van Harmelen
parent d2eab5a760
commit 2531ab024a
9 changed files with 640 additions and 0 deletions

View File

@ -53,11 +53,13 @@ func Provider() terraform.ResourceProvider {
"cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(), "cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(),
"cloudstack_nic": resourceCloudStackNIC(), "cloudstack_nic": resourceCloudStackNIC(),
"cloudstack_port_forward": resourceCloudStackPortForward(), "cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_private_gateway": resourceCloudStackPrivateGateway(),
"cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(), "cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(),
"cloudstack_security_group": resourceCloudStackSecurityGroup(), "cloudstack_security_group": resourceCloudStackSecurityGroup(),
"cloudstack_security_group_rule": resourceCloudStackSecurityGroupRule(), "cloudstack_security_group_rule": resourceCloudStackSecurityGroupRule(),
"cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(), "cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(),
"cloudstack_static_nat": resourceCloudStackStaticNAT(), "cloudstack_static_nat": resourceCloudStackStaticNAT(),
"cloudstack_static_route": resourceCloudStackStaticRoute(),
"cloudstack_template": resourceCloudStackTemplate(), "cloudstack_template": resourceCloudStackTemplate(),
"cloudstack_vpc": resourceCloudStackVPC(), "cloudstack_vpc": resourceCloudStackVPC(),
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(), "cloudstack_vpn_connection": resourceCloudStackVPNConnection(),

View File

@ -144,6 +144,21 @@ func testAccPreCheck(t *testing.T) {
if v := os.Getenv("CLOUDSTACK_ZONE"); v == "" { if v := os.Getenv("CLOUDSTACK_ZONE"); v == "" {
t.Fatal("CLOUDSTACK_ZONE must be set for acceptance tests") t.Fatal("CLOUDSTACK_ZONE must be set for acceptance tests")
} }
if v := os.Getenv("CLOUDSTACK_PRIVGW_GATEWAY"); v == "" {
t.Fatal("CLOUDSTACK_PRIVGW_GATEWAY must be set for acceptance tests")
}
if v := os.Getenv("CLOUDSTACK_PRIVGW_IPADDRESS"); v == "" {
t.Fatal("CLOUDSTACK_PRIVGW_IPADDRESS must be set for acceptance tests")
}
if v := os.Getenv("CLOUDSTACK_PRIVGW_NETMASK"); v == "" {
t.Fatal("CLOUDSTACK_PRIVGW_NETMASK must be set for acceptance tests")
}
if v := os.Getenv("CLOUDSTACK_PRIVGW_VLAN"); v == "" {
t.Fatal("CLOUDSTACK_PRIVGW_VLAN must be set for acceptance tests")
}
if v := os.Getenv("CLOUDSTACK_STATIC_ROUTE_CIDR"); v == "" {
t.Fatal("CLOUDSTACK_STATIC_ROUTE_CIDR must be set for acceptance tests")
}
} }
// Name of a valid disk offering // Name of a valid disk offering
@ -223,3 +238,12 @@ var CLOUDSTACK_PROJECT_NETWORK = os.Getenv("CLOUDSTACK_PROJECT_NETWORK")
// Name of a zone that exists already // Name of a zone that exists already
var CLOUDSTACK_ZONE = os.Getenv("CLOUDSTACK_ZONE") var CLOUDSTACK_ZONE = os.Getenv("CLOUDSTACK_ZONE")
// Details of the private gateway that will be added to VPC testing this, should be done using ROOT keys
var CLOUDSTACK_PRIVGW_GATEWAY = os.Getenv("CLOUDSTACK_PRIVGW_GATEWAY")
var CLOUDSTACK_PRIVGW_IPADDRESS = os.Getenv("CLOUDSTACK_PRIVGW_IPADDRESS")
var CLOUDSTACK_PRIVGW_NETMASK = os.Getenv("CLOUDSTACK_PRIVGW_NETMASK")
var CLOUDSTACK_PRIVGW_VLAN = os.Getenv("CLOUDSTACK_PRIVGW_VLAN")
// Details of the static route that will be added to private gateway testing this.
var CLOUDSTACK_STATIC_ROUTE_CIDR = os.Getenv("CLOUDSTACK_STATIC_ROUTE_CIDR")

View File

@ -0,0 +1,173 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackPrivateGateway() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackPrivateGatewayCreate,
Read: resourceCloudStackPrivateGatewayRead,
Update: resourceCloudStackPrivateGatewayUpdate,
Delete: resourceCloudStackPrivateGatewayDelete,
Schema: map[string]*schema.Schema{
"gateway": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"ip_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"netmask": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"vlan": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"physical_network_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"network_offering": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"acl_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"vpc_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackPrivateGatewayCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
ipaddress := d.Get("ip_address").(string)
networkofferingid := d.Get("network_offering").(string)
// Create a new parameter struct
p := cs.VPC.NewCreatePrivateGatewayParams(
d.Get("gateway").(string),
ipaddress,
d.Get("netmask").(string),
d.Get("vlan").(string),
d.Get("vpc_id").(string),
)
// Retrieve the network_offering ID
if networkofferingid != "" {
networkofferingid, e := retrieveID(cs, "network_offering", networkofferingid)
if e != nil {
return e.Error()
}
p.SetNetworkofferingid(networkofferingid)
}
// Check if we want to associate an ACL
if aclid, ok := d.GetOk("acl_id"); ok {
// Set the acl ID
p.SetAclid(aclid.(string))
}
// Create the new private gateway
r, err := cs.VPC.CreatePrivateGateway(p)
if err != nil {
return fmt.Errorf("Error creating private gateway for %s: %s", ipaddress, err)
}
d.SetId(r.Id)
return resourceCloudStackPrivateGatewayRead(d, meta)
}
func resourceCloudStackPrivateGatewayRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the private gateway details
gw, count, err := cs.VPC.GetPrivateGatewayByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] Private gateway %s does no longer exist", d.Id())
d.SetId("")
return nil
}
return err
}
d.Set("gateway", gw.Gateway)
d.Set("ip_address", gw.Ipaddress)
d.Set("netmask", gw.Netmask)
d.Set("vlan", gw.Vlan)
d.Set("acl_id", gw.Aclid)
d.Set("vpc_id", gw.Vpcid)
return nil
}
func resourceCloudStackPrivateGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Replace the ACL if the ID has changed
if d.HasChange("acl_id") {
p := cs.NetworkACL.NewReplaceNetworkACLListParams(d.Get("acl_id").(string))
p.SetNetworkid(d.Id())
_, err := cs.NetworkACL.ReplaceNetworkACLList(p)
if err != nil {
return fmt.Errorf("Error replacing ACL: %s", err)
}
}
return resourceCloudStackNetworkRead(d, meta)
}
func resourceCloudStackPrivateGatewayDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPC.NewDeletePrivateGatewayParams(d.Id())
// Delete the private gateway
_, err := cs.VPC.DeletePrivateGateway(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 deleting private gateway %s: %s", d.Id(), err)
}
return nil
}

View File

@ -0,0 +1,123 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackPrivateGateway_basic(t *testing.T) {
var gateway cloudstack.PrivateGateway
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackPrivateGatewayDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackPrivateGateway_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackPrivateGatewayExists(
"cloudstack_private_gateway.foo", &gateway),
testAccCheckCloudStackPrivateGatewayAttributes(&gateway),
),
},
},
})
}
func testAccCheckCloudStackPrivateGatewayExists(
n string, gateway *cloudstack.PrivateGateway) 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 Private Gateway ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
pgw, _, err := cs.VPC.GetPrivateGatewayByID(rs.Primary.ID)
if err != nil {
return err
}
if pgw.Id != rs.Primary.ID {
return fmt.Errorf("Private Gateway not found")
}
*gateway = *pgw
return nil
}
}
func testAccCheckCloudStackPrivateGatewayAttributes(
gateway *cloudstack.PrivateGateway) resource.TestCheckFunc {
return func(s *terraform.State) error {
if gateway.Gateway != CLOUDSTACK_PRIVGW_GATEWAY {
return fmt.Errorf("Bad Gateway: %s", gateway.Gateway)
}
if gateway.Ipaddress != CLOUDSTACK_PRIVGW_IPADDRESS {
return fmt.Errorf("Bad Gateway: %s", gateway.Ipaddress)
}
if gateway.Netmask != CLOUDSTACK_PRIVGW_NETMASK {
return fmt.Errorf("Bad Gateway: %s", gateway.Netmask)
}
return nil
}
}
func testAccCheckCloudStackPrivateGatewayDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_private_gateway" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No private gateway ID is set")
}
gateway, _, err := cs.VPC.GetPrivateGatewayByID(rs.Primary.ID)
if err == nil && gateway.Id != "" {
return fmt.Errorf("Private gateway %s still exists", rs.Primary.ID)
}
}
return nil
}
var testAccCloudStackPrivateGateway_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_private_gateway" "foo" {
gateway = "%s"
ip_address = "%s"
netmask = "%s"
vlan = "%s"
vpc_id = "${cloudstack_vpc.foobar.id}"
}`,
CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE,
CLOUDSTACK_PRIVGW_GATEWAY,
CLOUDSTACK_PRIVGW_IPADDRESS,
CLOUDSTACK_PRIVGW_NETMASK,
CLOUDSTACK_PRIVGW_VLAN)

View File

@ -0,0 +1,94 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackStaticRoute() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackStaticRouteCreate,
Read: resourceCloudStackStaticRouteRead,
Delete: resourceCloudStackStaticRouteDelete,
Schema: map[string]*schema.Schema{
"cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"gateway_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackStaticRouteCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPC.NewCreateStaticRouteParams(
d.Get("cidr").(string),
d.Get("gateway_id").(string),
)
// Create the new private gateway
r, err := cs.VPC.CreateStaticRoute(p)
if err != nil {
return fmt.Errorf("Error creating static route for %s: %s", d.Get("cidr").(string), err)
}
d.SetId(r.Id)
return resourceCloudStackStaticRouteRead(d, meta)
}
func resourceCloudStackStaticRouteRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the virtual machine details
staticroute, count, err := cs.VPC.GetStaticRouteByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] Static route %s does no longer exist", d.Id())
d.SetId("")
return nil
}
return err
}
d.Set("cidr", staticroute.Cidr)
return nil
}
func resourceCloudStackStaticRouteDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPC.NewDeleteStaticRouteParams(d.Id())
// Delete the private gateway
_, err := cs.VPC.DeleteStaticRoute(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 deleting static route for %s: %s", d.Get("cidr").(string), err)
}
return nil
}

View File

@ -0,0 +1,121 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackStaticRoute_basic(t *testing.T) {
var staticroute cloudstack.StaticRoute
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackStaticRouteDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackStaticRoute_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackStaticRouteExists(
"cloudstack_static_route.bar", &staticroute),
testAccCheckCloudStackStaticRouteAttributes(&staticroute),
),
},
},
})
}
func testAccCheckCloudStackStaticRouteExists(
n string, staticroute *cloudstack.StaticRoute) 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 Route ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
route, _, err := cs.VPC.GetStaticRouteByID(rs.Primary.ID)
if err != nil {
return err
}
if route.Id != rs.Primary.ID {
return fmt.Errorf("Static Route not found")
}
*staticroute = *route
return nil
}
}
func testAccCheckCloudStackStaticRouteAttributes(
staticroute *cloudstack.StaticRoute) resource.TestCheckFunc {
return func(s *terraform.State) error {
if staticroute.Cidr != CLOUDSTACK_STATIC_ROUTE_CIDR {
return fmt.Errorf("Bad Cidr: %s", staticroute.Cidr)
}
return nil
}
}
func testAccCheckCloudStackStaticRouteDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_static_route" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No static route ID is set")
}
staticroute, _, err := cs.VPC.GetStaticRouteByID(rs.Primary.ID)
if err == nil && staticroute.Id != "" {
return fmt.Errorf("Static route %s still exists", rs.Primary.ID)
}
}
return nil
}
var testAccCloudStackStaticRoute_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_private_gateway" "foo" {
gateway = "%s"
ip_address = "%s"
netmask = "%s"
vlan = "%s"
vpc_id = "${cloudstack_vpc.foobar.id}"
}
resource "cloudstack_static_route" "bar" {
cidr = "%s"
gateway_id = "${cloudstack_private_gateway.foo.id}"
}`,
CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE,
CLOUDSTACK_PRIVGW_GATEWAY,
CLOUDSTACK_PRIVGW_IPADDRESS,
CLOUDSTACK_PRIVGW_NETMASK,
CLOUDSTACK_PRIVGW_VLAN,
CLOUDSTACK_STATIC_ROUTE_CIDR)

View File

@ -0,0 +1,58 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_private_gateway"
sidebar_current: "docs-cloudstack-resource-private-gateway"
description: |-
Creates a private gateway.
---
# cloudstack\_private\_gateway
Creates a private gateway for the given VPC.
*NOTE: private gateway can only be created using a ROOT account!*
## Example Usage
```
resource "cloudstack_private_gateway" "default" {
gateway = 10.0.0.1
ip_address = "10.0.0.2"
netmask = "255.255.255.252"
vlan = "200"
vpc_id = "76f6e8dc-07e3-4971-b2a2-8831b0cc4cb4"
}
```
## Argument Reference
The following arguments are supported:
* `gateway` - (Required) the gateway of the Private gateway. Changing this
forces a new resource to be created.
* `ip_address` - (Required) the IP address of the Private gateway. Changing this forces
a new resource to be created.
* `netmask` - (Required) The netmask of the Private gateway. Changing
this forces a new resource to be created.
* `vlan` - (Required) The VLAN number (1-4095) the network will use.
* `physical_network_id` - (Optional) The ID of the physical network this private
gateway belongs to.
* `network_offering` - (Optional) The name or ID of the network offering to use for
the private gateways network connection.
* `acl_id` - (Required) The ACL ID that should be attached to the network.
* `vpc_id` - (Required) The VPC ID in which to create this Private gateway. Changing
this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the private gateway.

View File

@ -0,0 +1,37 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_static_route"
sidebar_current: "docs-cloudstack-resource-static-route"
description: |-
Creates a static route.
---
# cloudstack\_static\_route
Creates a static route for the given private gateway or VPC.
## Example Usage
```
resource "cloudstack_static_route" "default" {
cidr = "10.0.0.0/16"
gateway_id = "76f607e3-e8dc-4971-8831-b2a2b0cc4cb4"
}
```
## Argument Reference
The following arguments are supported:
* `cidr` - (Required) The CIDR for the static route. Changing this forces
a new resource to be created.
* `gateway_id` - (Required) The ID of the Private gateway. Changing this forces
a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the static route.

View File

@ -61,6 +61,10 @@
<a href="/docs/providers/cloudstack/r/port_forward.html">cloudstack_port_forward</a> <a href="/docs/providers/cloudstack/r/port_forward.html">cloudstack_port_forward</a>
</li> </li>
<li<%= sidebar_current("docs-cloudstack-resource-private-gateway") %>>
<a href="/docs/providers/cloudstack/r/private_gateway.html">cloudstack_private_gateway</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-secondary-ipaddress") %>> <li<%= sidebar_current("docs-cloudstack-resource-secondary-ipaddress") %>>
<a href="/docs/providers/cloudstack/r/secondary_ipaddress.html">cloudstack_secondary_ipaddress</a> <a href="/docs/providers/cloudstack/r/secondary_ipaddress.html">cloudstack_secondary_ipaddress</a>
</li> </li>
@ -81,6 +85,10 @@
<a href="/docs/providers/cloudstack/r/static_nat.html">cloudstack_static_nat</a> <a href="/docs/providers/cloudstack/r/static_nat.html">cloudstack_static_nat</a>
</li> </li>
<li<%= sidebar_current("docs-cloudstack-resource-static-route") %>>
<a href="/docs/providers/cloudstack/r/static_route.html">cloudstack_static_route</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-template") %>> <li<%= sidebar_current("docs-cloudstack-resource-template") %>>
<a href="/docs/providers/cloudstack/r/template.html">cloudstack_template</a> <a href="/docs/providers/cloudstack/r/template.html">cloudstack_template</a>
</li> </li>