First release of a provider for CloudStack

Of course not all resources are covered by this first release, but
there should be enough resources available to handle most common
operations.

Tests and docs are included.
This commit is contained in:
Sander van Harmelen 2014-12-10 22:20:52 +01:00
parent 633a08230e
commit 4910423d83
38 changed files with 5767 additions and 0 deletions

View File

@ -0,0 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/cloudstack"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: cloudstack.Provider,
})
}

View File

@ -0,0 +1 @@
package main

View File

@ -0,0 +1,18 @@
package cloudstack
import "github.com/xanzy/go-cloudstack/cloudstack"
// Config is the configuration structure used to instantiate a
// new CloudStack client.
type Config struct {
ApiURL string
ApiKey string
SecretKey string
}
// Client() returns a new CloudStack client.
func (c *Config) NewClient() (*cloudstack.CloudStackClient, error) {
cs := cloudstack.NewAsyncClient(c.ApiURL, c.ApiKey, c.SecretKey, false)
cs.AsyncTimeout(180)
return cs, nil
}

View File

@ -0,0 +1,68 @@
package cloudstack
import (
"os"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"api_url": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: envDefaultFunc("CLOUDSTACK_API_URL"),
},
"api_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: envDefaultFunc("CLOUDSTACK_API_KEY"),
},
"secret_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: envDefaultFunc("CLOUDSTACK_SECRET_KEY"),
},
},
ResourcesMap: map[string]*schema.Resource{
"cloudstack_disk": resourceCloudStackDisk(),
"cloudstack_firewall": resourceCloudStackFirewall(),
"cloudstack_instance": resourceCloudStackInstance(),
"cloudstack_ipaddress": resourceCloudStackIPAddress(),
"cloudstack_network": resourceCloudStackNetwork(),
"cloudstack_network_acl": resourceCloudStackNetworkACL(),
"cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(),
"cloudstack_nic": resourceCloudStackNIC(),
"cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_vpc": resourceCloudStackVPC(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
ApiURL: d.Get("api_url").(string),
ApiKey: d.Get("api_key").(string),
SecretKey: d.Get("secret_key").(string),
}
return config.NewClient()
}
func envDefaultFunc(k string) schema.SchemaDefaultFunc {
return func() (interface{}, error) {
if v := os.Getenv(k); v != "" {
return v, nil
}
return nil, nil
}
}

View File

@ -0,0 +1,182 @@
package cloudstack
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"cloudstack": 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) {
if v := os.Getenv("CLOUDSTACK_API_URL"); v == "" {
t.Fatal("CLOUDSTACK_API_URL must be set for acceptance tests")
}
if v := os.Getenv("CLOUDSTACK_API_KEY"); v == "" {
t.Fatal("CLOUDSTACK_API_KEY must be set for acceptance tests")
}
if v := os.Getenv("CLOUDSTACK_SECRET_KEY"); v == "" {
t.Fatal("CLOUDSTACK_SECRET_KEY must be set for acceptance tests")
}
// Testing all environment/installation specific variables which are needed
// to run all the acceptance tests
if CLOUDSTACK_DISK_OFFERING_1 == "" {
if v := os.Getenv("CLOUDSTACK_DISK_OFFERING_1"); v == "" {
t.Fatal("CLOUDSTACK_DISK_OFFERING_1 must be set for acceptance tests")
} else {
CLOUDSTACK_DISK_OFFERING_1 = v
}
}
if CLOUDSTACK_DISK_OFFERING_2 == "" {
if v := os.Getenv("CLOUDSTACK_DISK_OFFERING_2"); v == "" {
t.Fatal("CLOUDSTACK_DISK_OFFERING_2 must be set for acceptance tests")
} else {
CLOUDSTACK_DISK_OFFERING_2 = v
}
}
if CLOUDSTACK_SERVICE_OFFERING_1 == "" {
if v := os.Getenv("CLOUDSTACK_SERVICE_OFFERING_1"); v == "" {
t.Fatal("CLOUDSTACK_SERVICE_OFFERING_1 must be set for acceptance tests")
} else {
CLOUDSTACK_SERVICE_OFFERING_1 = v
}
}
if CLOUDSTACK_SERVICE_OFFERING_2 == "" {
if v := os.Getenv("CLOUDSTACK_SERVICE_OFFERING_2"); v == "" {
t.Fatal("CLOUDSTACK_SERVICE_OFFERING_2 must be set for acceptance tests")
} else {
CLOUDSTACK_SERVICE_OFFERING_2 = v
}
}
if CLOUDSTACK_NETWORK_1 == "" {
if v := os.Getenv("CLOUDSTACK_NETWORK_1"); v == "" {
t.Fatal("CLOUDSTACK_NETWORK_1 must be set for acceptance tests")
} else {
CLOUDSTACK_NETWORK_1 = v
}
}
if CLOUDSTACK_NETWORK_1_CIDR == "" {
if v := os.Getenv("CLOUDSTACK_NETWORK_1_CIDR"); v == "" {
t.Fatal("CLOUDSTACK_NETWORK_1_CIDR must be set for acceptance tests")
} else {
CLOUDSTACK_NETWORK_1_CIDR = v
}
}
if CLOUDSTACK_NETWORK_1_OFFERING == "" {
if v := os.Getenv("CLOUDSTACK_NETWORK_1_OFFERING"); v == "" {
t.Fatal("CLOUDSTACK_NETWORK_1_OFFERING must be set for acceptance tests")
} else {
CLOUDSTACK_NETWORK_1_OFFERING = v
}
}
if CLOUDSTACK_NETWORK_1_IPADDRESS == "" {
if v := os.Getenv("CLOUDSTACK_NETWORK_1_IPADDRESS"); v == "" {
t.Fatal("CLOUDSTACK_NETWORK_1_IPADDRESS must be set for acceptance tests")
} else {
CLOUDSTACK_NETWORK_1_IPADDRESS = v
}
}
if CLOUDSTACK_NETWORK_2 == "" {
if v := os.Getenv("CLOUDSTACK_NETWORK_2"); v == "" {
t.Fatal("CLOUDSTACK_NETWORK_2 must be set for acceptance tests")
} else {
CLOUDSTACK_NETWORK_2 = v
}
}
if CLOUDSTACK_NETWORK_2_IPADDRESS == "" {
if v := os.Getenv("CLOUDSTACK_NETWORK_2_IPADDRESS"); v == "" {
t.Fatal("CLOUDSTACK_NETWORK_2_IPADDRESS must be set for acceptance tests")
} else {
CLOUDSTACK_NETWORK_2_IPADDRESS = v
}
}
if CLOUDSTACK_VPC_CIDR == "" {
if v := os.Getenv("CLOUDSTACK_VPC_CIDR"); v == "" {
t.Fatal("CLOUDSTACK_VPC_CIDR must be set for acceptance tests")
} else {
CLOUDSTACK_VPC_CIDR = v
}
}
if CLOUDSTACK_VPC_OFFERING == "" {
if v := os.Getenv("CLOUDSTACK_VPC_OFFERING"); v == "" {
t.Fatal("CLOUDSTACK_VPC_OFFERING must be set for acceptance tests")
} else {
CLOUDSTACK_VPC_OFFERING = v
}
}
if CLOUDSTACK_VPC_NETWORK_CIDR == "" {
if v := os.Getenv("CLOUDSTACK_VPC_NETWORK_CIDR"); v == "" {
t.Fatal("CLOUDSTACK_VPC_NETWORK_CIDR must be set for acceptance tests")
} else {
CLOUDSTACK_VPC_NETWORK_CIDR = v
}
}
if CLOUDSTACK_VPC_NETWORK_OFFERING == "" {
if v := os.Getenv("CLOUDSTACK_VPC_NETWORK_OFFERING"); v == "" {
t.Fatal("CLOUDSTACK_VPC_NETWORK_OFFERING must be set for acceptance tests")
} else {
CLOUDSTACK_VPC_NETWORK_OFFERING = v
}
}
if CLOUDSTACK_PUBLIC_IPADDRESS == "" {
if v := os.Getenv("CLOUDSTACK_PUBLIC_IPADDRESS"); v == "" {
t.Fatal("CLOUDSTACK_PUBLIC_IPADDRESS must be set for acceptance tests")
} else {
CLOUDSTACK_PUBLIC_IPADDRESS = v
}
}
if CLOUDSTACK_TEMPLATE == "" {
if v := os.Getenv("CLOUDSTACK_TEMPLATE"); v == "" {
t.Fatal("CLOUDSTACK_TEMPLATE must be set for acceptance tests")
} else {
CLOUDSTACK_TEMPLATE = v
}
}
if CLOUDSTACK_ZONE == "" {
if v := os.Getenv("CLOUDSTACK_ZONE"); v == "" {
t.Fatal("CLOUDSTACK_ZONE must be set for acceptance tests")
} else {
CLOUDSTACK_ZONE = v
}
}
}
// EITHER SET THESE, OR ADD THE VALUES TO YOUR ENV!!
var CLOUDSTACK_DISK_OFFERING_1 = ""
var CLOUDSTACK_DISK_OFFERING_2 = ""
var CLOUDSTACK_SERVICE_OFFERING_1 = ""
var CLOUDSTACK_SERVICE_OFFERING_2 = ""
var CLOUDSTACK_NETWORK_1 = ""
var CLOUDSTACK_NETWORK_1_CIDR = ""
var CLOUDSTACK_NETWORK_1_OFFERING = ""
var CLOUDSTACK_NETWORK_1_IPADDRESS = ""
var CLOUDSTACK_NETWORK_2 = ""
var CLOUDSTACK_NETWORK_2_IPADDRESS = ""
var CLOUDSTACK_VPC_CIDR = ""
var CLOUDSTACK_VPC_OFFERING = ""
var CLOUDSTACK_VPC_NETWORK_CIDR = ""
var CLOUDSTACK_VPC_NETWORK_OFFERING = ""
var CLOUDSTACK_PUBLIC_IPADDRESS = ""
var CLOUDSTACK_TEMPLATE = ""
var CLOUDSTACK_ZONE = ""

View File

@ -0,0 +1,496 @@
package cloudstack
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackDisk() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackDiskCreate,
Read: resourceCloudStackDiskRead,
Update: resourceCloudStackDiskUpdate,
Delete: resourceCloudStackDiskDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"attach": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"device": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"disk_offering": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"size": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"shrink_ok": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"virtual_machine": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
d.Partial(true)
name := d.Get("name").(string)
// Create a new parameter struct
p := cs.Volume.NewCreateVolumeParams(name)
// Retrieve the disk_offering UUID
diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))
if e != nil {
return e.Error()
}
// Set the disk_offering UUID
p.SetDiskofferingid(diskofferingid)
if d.Get("size").(int) != 0 {
// Set the volume size
p.SetSize(d.Get("size").(int))
}
// Retrieve the zone UUID
zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
if e != nil {
return e.Error()
}
// Set the zone ID
p.SetZoneid(zoneid)
// Create the new volume
r, err := cs.Volume.CreateVolume(p)
if err != nil {
return fmt.Errorf("Error creating the new disk %s: %s", name, err)
}
// Set the volume UUID and partials
d.SetId(r.Id)
d.SetPartial("name")
d.SetPartial("device")
d.SetPartial("disk_offering")
d.SetPartial("size")
d.SetPartial("virtual_machine")
d.SetPartial("zone")
if d.Get("attach").(bool) {
err := resourceCloudStackDiskAttach(d, meta)
if err != nil {
return fmt.Errorf("Error attaching the new disk %s to virtual machine: %s", name, err)
}
// Set the additional partial
d.SetPartial("attach")
}
d.Partial(false)
return resourceCloudStackDiskRead(d, meta)
}
func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the volume details
v, count, err := cs.Volume.GetVolumeByID(d.Id())
if err != nil {
if count == 0 {
d.SetId("")
return nil
}
return err
}
d.Set("name", v.Name)
d.Set("attach", v.Attached != "") // If attached this will contain a timestamp when attached
d.Set("disk_offering", v.Diskofferingname)
d.Set("size", v.Size/(1024*1024*1024)) // Needed to get GB's again
d.Set("zone", v.Zonename)
if v.Attached != "" {
// Get the virtual machine details
vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(v.Virtualmachineid)
if err != nil {
return err
}
// Get the guest OS type details
os, _, err := cs.GuestOS.GetOsTypeByID(vm.Guestosid)
if err != nil {
return err
}
// Get the guest OS category details
c, _, err := cs.GuestOS.GetOsCategoryByID(os.Oscategoryid)
if err != nil {
return err
}
d.Set("device", retrieveDeviceName(v.Deviceid, c.Name))
d.Set("virtual_machine", v.Vmname)
}
return nil
}
func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
d.Partial(true)
name := d.Get("name").(string)
if d.HasChange("disk_offering") || d.HasChange("size") {
// Detach the volume (re-attach is done at the end of this function)
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
}
// Create a new parameter struct
p := cs.Volume.NewResizeVolumeParams()
// Set the volume UUID
p.SetId(d.Id())
// Retrieve the disk_offering UUID
diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))
if e != nil {
return e.Error()
}
// Set the disk_offering UUID
p.SetDiskofferingid(diskofferingid)
if d.Get("size").(int) != 0 {
// Set the size
p.SetSize(d.Get("size").(int))
}
// Set the shrink bit
p.SetShrinkok(d.Get("shrink_ok").(bool))
// Change the disk_offering
r, err := cs.Volume.ResizeVolume(p)
if err != nil {
return fmt.Errorf("Error changing disk offering/size for disk %s: %s", name, err)
}
// Update the volume UUID and set partials
d.SetId(r.Id)
d.SetPartial("disk_offering")
d.SetPartial("size")
}
// If the device changed, just detach here so we can re-attach the
// volume at the end of this function
if d.HasChange("device") || d.HasChange("virtual_machine") {
// Detach the volume
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
}
}
if d.Get("attach").(bool) {
// Attach the volume
err := resourceCloudStackDiskAttach(d, meta)
if err != nil {
return fmt.Errorf("Error attaching disk %s to virtual machine: %s", name, err)
}
// Set the additional partials
d.SetPartial("attach")
d.SetPartial("device")
d.SetPartial("virtual_machine")
} else {
// Detach the volume
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
return fmt.Errorf("Error detaching disk %s from virtual machine: %s", name, err)
}
}
d.Partial(false)
return resourceCloudStackDiskRead(d, meta)
}
func resourceCloudStackDiskDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Detach the volume
if err := resourceCloudStackDiskDetach(d, meta); err != nil {
return err
}
// Create a new parameter struct
p := cs.Volume.NewDeleteVolumeParams(d.Id())
// Delete the voluem
if _, err := cs.Volume.DeleteVolume(p); err != nil {
// This is a very poor way to be told the UUID 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 err
}
return nil
}
func resourceCloudStackDiskAttach(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// First check if the disk isn't already attached
if attached, err := isAttached(cs, d.Id()); err != nil || attached {
return err
}
// Retrieve the virtual_machine UUID
virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.Volume.NewAttachVolumeParams(d.Id(), virtualmachineid)
if device, ok := d.GetOk("device"); ok {
// Retrieve the device ID
deviceid := retrieveDeviceID(device.(string))
if deviceid == -1 {
return fmt.Errorf("Device %s is not a valid device", device.(string))
}
// Set the device ID
p.SetDeviceid(deviceid)
}
// Attach the new volume
r, err := cs.Volume.AttachVolume(p)
if err != nil {
return err
}
d.SetId(r.Id)
return nil
}
func resourceCloudStackDiskDetach(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Check if the volume is actually attached, before detaching
if attached, err := isAttached(cs, d.Id()); err != nil || !attached {
return err
}
// Create a new parameter struct
p := cs.Volume.NewDetachVolumeParams()
// Set the volume UUID
p.SetId(d.Id())
// Detach the currently attached volume
if _, err := cs.Volume.DetachVolume(p); err != nil {
// Retrieve the virtual_machine UUID
virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
pd := cs.VirtualMachine.NewStopVirtualMachineParams(virtualmachineid)
// Stop the virtual machine in order to be able to detach the disk
if _, err := cs.VirtualMachine.StopVirtualMachine(pd); err != nil {
return err
}
// Try again to detach the currently attached volume
if _, err := cs.Volume.DetachVolume(p); err != nil {
return err
}
// Create a new parameter struct
pu := cs.VirtualMachine.NewStartVirtualMachineParams(virtualmachineid)
// Start the virtual machine again
if _, err := cs.VirtualMachine.StartVirtualMachine(pu); err != nil {
return err
}
}
return nil
}
func isAttached(cs *cloudstack.CloudStackClient, id string) (bool, error) {
// Get the volume details
v, _, err := cs.Volume.GetVolumeByID(id)
if err != nil {
return false, err
}
return v.Attached != "", nil
}
func retrieveDeviceID(device string) int {
switch device {
case "/dev/xvdb", "D:":
return 1
case "/dev/xvdc", "E:":
return 2
case "/dev/xvde", "F:":
return 4
case "/dev/xvdf", "G:":
return 5
case "/dev/xvdg", "H:":
return 6
case "/dev/xvdh", "I:":
return 7
case "/dev/xvdi", "J:":
return 8
case "/dev/xvdj", "K:":
return 9
case "/dev/xvdk", "L:":
return 10
case "/dev/xvdl", "M:":
return 11
case "/dev/xvdm", "N:":
return 12
case "/dev/xvdn", "O:":
return 13
case "/dev/xvdo", "P:":
return 14
case "/dev/xvdp", "Q:":
return 15
default:
return -1
}
}
func retrieveDeviceName(device int, os string) string {
switch device {
case 1:
if os == "Windows" {
return "D:"
} else {
return "/dev/xvdb"
}
case 2:
if os == "Windows" {
return "E:"
} else {
return "/dev/xvdc"
}
case 4:
if os == "Windows" {
return "F:"
} else {
return "/dev/xvde"
}
case 5:
if os == "Windows" {
return "G:"
} else {
return "/dev/xvdf"
}
case 6:
if os == "Windows" {
return "H:"
} else {
return "/dev/xvdg"
}
case 7:
if os == "Windows" {
return "I:"
} else {
return "/dev/xvdh"
}
case 8:
if os == "Windows" {
return "J:"
} else {
return "/dev/xvdi"
}
case 9:
if os == "Windows" {
return "K:"
} else {
return "/dev/xvdj"
}
case 10:
if os == "Windows" {
return "L:"
} else {
return "/dev/xvdk"
}
case 11:
if os == "Windows" {
return "M:"
} else {
return "/dev/xvdl"
}
case 12:
if os == "Windows" {
return "N:"
} else {
return "/dev/xvdm"
}
case 13:
if os == "Windows" {
return "O:"
} else {
return "/dev/xvdn"
}
case 14:
if os == "Windows" {
return "P:"
} else {
return "/dev/xvdo"
}
case 15:
if os == "Windows" {
return "Q:"
} else {
return "/dev/xvdp"
}
default:
return "unknown"
}
}

View File

@ -0,0 +1,292 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackDisk_basic(t *testing.T) {
var disk cloudstack.Volume
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackDiskDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackDisk_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackDiskExists(
"cloudstack_disk.foo", &disk),
testAccCheckCloudStackDiskAttributes(&disk),
),
},
},
})
}
func TestAccCloudStackDisk_device(t *testing.T) {
var disk cloudstack.Volume
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackDiskDestroyAdvanced,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackDisk_device,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackDiskExists(
"cloudstack_disk.foo", &disk),
testAccCheckCloudStackDiskAttributes(&disk),
resource.TestCheckResourceAttr(
"cloudstack_disk.foo", "device", "/dev/xvde"),
),
},
},
})
}
func TestAccCloudStackDisk_update(t *testing.T) {
var disk cloudstack.Volume
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackDiskDestroyAdvanced,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackDisk_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackDiskExists(
"cloudstack_disk.foo", &disk),
testAccCheckCloudStackDiskAttributes(&disk),
),
},
resource.TestStep{
Config: testAccCloudStackDisk_resize,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackDiskExists(
"cloudstack_disk.foo", &disk),
testAccCheckCloudStackDiskResized(&disk),
resource.TestCheckResourceAttr(
"cloudstack_disk.foo", "disk_offering", CLOUDSTACK_DISK_OFFERING_2),
),
},
},
})
}
func testAccCheckCloudStackDiskExists(
n string, disk *cloudstack.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 disk ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
volume, _, err := cs.Volume.GetVolumeByID(rs.Primary.ID)
if err != nil {
return err
}
if volume.Id != rs.Primary.ID {
return fmt.Errorf("Disk not found")
}
*disk = *volume
return nil
}
}
func testAccCheckCloudStackDiskAttributes(
disk *cloudstack.Volume) resource.TestCheckFunc {
return func(s *terraform.State) error {
if disk.Name != "terraform-disk" {
return fmt.Errorf("Bad name: %s", disk.Name)
}
if disk.Diskofferingname != CLOUDSTACK_DISK_OFFERING_1 {
return fmt.Errorf("Bad disk offering: %s", disk.Diskofferingname)
}
return nil
}
}
func testAccCheckCloudStackDiskResized(
disk *cloudstack.Volume) resource.TestCheckFunc {
return func(s *terraform.State) error {
if disk.Diskofferingname != CLOUDSTACK_DISK_OFFERING_2 {
return fmt.Errorf("Bad disk offering: %s", disk.Diskofferingname)
}
return nil
}
}
func testAccCheckCloudStackDiskDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_disk" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No disk ID is set")
}
p := cs.Volume.NewDeleteVolumeParams(rs.Primary.ID)
err, _ := cs.Volume.DeleteVolume(p)
if err != nil {
return fmt.Errorf(
"Error deleting disk (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
func testAccCheckCloudStackDiskDestroyAdvanced(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_disk" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No disk ID is set")
}
p := cs.Volume.NewDeleteVolumeParams(rs.Primary.ID)
err, _ := cs.Volume.DeleteVolume(p)
if err != nil {
return fmt.Errorf(
"Error deleting disk (%s): %s",
rs.Primary.ID, err)
}
}
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_instance" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}
p := cs.VirtualMachine.NewDestroyVirtualMachineParams(rs.Primary.ID)
err, _ := cs.VirtualMachine.DestroyVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error deleting instance (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackDisk_basic = fmt.Sprintf(`
resource "cloudstack_disk" "foo" {
name = "terraform-disk"
attach = false
disk_offering = "%s"
zone = "%s"
}`,
CLOUDSTACK_DISK_OFFERING_1,
CLOUDSTACK_ZONE)
var testAccCloudStackDisk_device = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
expunge = true
}
resource "cloudstack_disk" "foo" {
name = "terraform-disk"
attach = true
device = "/dev/xvde"
disk_offering = "%s"
virtual_machine = "${cloudstack_instance.foobar.name}"
zone = "${cloudstack_instance.foobar.zone}"
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_DISK_OFFERING_1)
var testAccCloudStackDisk_update = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
expunge = true
}
resource "cloudstack_disk" "foo" {
name = "terraform-disk"
attach = true
disk_offering = "%s"
virtual_machine = "${cloudstack_instance.foobar.name}"
zone = "${cloudstack_instance.foobar.zone}"
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_DISK_OFFERING_1)
var testAccCloudStackDisk_resize = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
expunge = true
}
resource "cloudstack_disk" "foo" {
name = "terraform-disk"
attach = true
disk_offering = "%s"
virtual_machine = "${cloudstack_instance.foobar.name}"
zone = "${cloudstack_instance.foobar.zone}"
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_DISK_OFFERING_2)

View File

@ -0,0 +1,442 @@
package cloudstack
import (
"bytes"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackFirewall() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackFirewallCreate,
Read: resourceCloudStackFirewallRead,
Update: resourceCloudStackFirewallUpdate,
Delete: resourceCloudStackFirewallDelete,
Schema: map[string]*schema.Schema{
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"rule": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"source_cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"icmp_type": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"icmp_code": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"ports": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
"uuids": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
},
},
},
Set: resourceCloudStackFirewallRuleHash,
},
},
}
}
func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the ipaddress UUID
ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
if e != nil {
return e.Error()
}
// We need to set this upfront in order to be able to save a partial state
d.SetId(d.Get("ipaddress").(string))
// Create all rules that are configured
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceCloudStackFirewallRuleHash,
}
for _, rule := range rs.List() {
// Create a single rule
err := resourceCloudStackFirewallCreateRule(d, meta, ipaddressid, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
d.Set("rule", rules)
if err != nil {
return err
}
}
}
return resourceCloudStackFirewallRead(d, meta)
}
func resourceCloudStackFirewallCreateRule(
d *schema.ResourceData, meta interface{}, ipaddressid string, rule map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
uuids := rule["uuids"].(map[string]interface{})
// Make sure all required parameters are there
if err := verifyFirewallParams(d, rule); err != nil {
return err
}
// Create a new parameter struct
p := cs.Firewall.NewCreateFirewallRuleParams(ipaddressid, rule["protocol"].(string))
// Set the CIDR list
p.SetCidrlist([]string{rule["source_cidr"].(string)})
// If the protocol is ICMP set the needed ICMP parameters
if rule["protocol"].(string) == "icmp" {
p.SetIcmptype(rule["icmp_type"].(int))
p.SetIcmpcode(rule["icmp_code"].(int))
r, err := cs.Firewall.CreateFirewallRule(p)
if err != nil {
return err
}
uuids["icmp"] = r.Id
rule["uuids"] = uuids
}
// If protocol is not ICMP, loop through all ports
if rule["protocol"].(string) != "icmp" {
if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
// Create an empty schema.Set to hold all processed ports
ports := &schema.Set{
F: func(v interface{}) int {
return hashcode.String(v.(string))
},
}
for _, port := range ps.List() {
re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
m := re.FindStringSubmatch(port.(string))
startPort, err := strconv.Atoi(m[1])
if err != nil {
return err
}
endPort := startPort
if m[2] != "" {
endPort, err = strconv.Atoi(m[2])
if err != nil {
return err
}
}
p.SetStartport(startPort)
p.SetEndport(endPort)
r, err := cs.Firewall.CreateFirewallRule(p)
if err != nil {
return err
}
ports.Add(port)
rule["ports"] = ports
uuids[port.(string)] = r.Id
rule["uuids"] = uuids
}
}
}
return nil
}
func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceCloudStackFirewallRuleHash,
}
// Read all rules that are configured
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
for _, rule := range rs.List() {
rule := rule.(map[string]interface{})
uuids := rule["uuids"].(map[string]interface{})
if rule["protocol"].(string) == "icmp" {
id, ok := uuids["icmp"]
if !ok {
continue
}
// Get the rule
r, count, err := cs.Firewall.GetFirewallRuleByID(id.(string))
// If the count == 0, there is no object found for this UUID
if err != nil {
if count == 0 {
delete(uuids, "icmp")
continue
}
return err
}
// Update the values
rule["source_cidr"] = r.Cidrlist
rule["protocol"] = r.Protocol
rule["icmp_type"] = r.Icmptype
rule["icmp_code"] = r.Icmpcode
rules.Add(rule)
}
// If protocol is not ICMP, loop through all ports
if rule["protocol"].(string) != "icmp" {
if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
// Create an empty schema.Set to hold all ports
ports := &schema.Set{
F: func(v interface{}) int {
return hashcode.String(v.(string))
},
}
// Loop through all ports and retrieve their info
for _, port := range ps.List() {
id, ok := uuids[port.(string)]
if !ok {
continue
}
// Get the rule
r, count, err := cs.Firewall.GetFirewallRuleByID(id.(string))
if err != nil {
if count == 0 {
delete(uuids, port.(string))
continue
}
return err
}
// Update the values
rule["source_cidr"] = r.Cidrlist
rule["protocol"] = r.Protocol
ports.Add(port)
}
// If there is at least one port found, add this rule to the rules set
if ports.Len() > 0 {
rule["ports"] = ports
rules.Add(rule)
}
}
}
}
}
if rules.Len() > 0 {
d.Set("rule", rules)
} else {
d.SetId("")
}
return nil
}
func resourceCloudStackFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the ipaddress UUID
ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
if e != nil {
return e.Error()
}
// Check if the rule set as a whole has changed
if d.HasChange("rule") {
o, n := d.GetChange("rule")
ors := o.(*schema.Set).Difference(n.(*schema.Set))
nrs := n.(*schema.Set).Difference(o.(*schema.Set))
// Now first loop through all the old rules and delete any obsolete ones
for _, rule := range ors.List() {
// Delete the rule as it no longer exists in the config
err := resourceCloudStackFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
if err != nil {
return err
}
}
// Make sure we save the state of the currently configured rules
rules := o.(*schema.Set).Intersection(n.(*schema.Set))
d.Set("rule", rules)
// Then loop through al the currently configured rules and create the new ones
for _, rule := range nrs.List() {
// When succesfully deleted, re-create it again if it still exists
err := resourceCloudStackFirewallCreateRule(
d, meta, ipaddressid, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
d.Set("rule", rules)
if err != nil {
return err
}
}
}
return resourceCloudStackFirewallRead(d, meta)
}
func resourceCloudStackFirewallDelete(d *schema.ResourceData, meta interface{}) error {
// Delete all rules
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
for _, rule := range rs.List() {
// Delete a single rule
err := resourceCloudStackFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
d.Set("rule", rs)
if err != nil {
return err
}
}
}
return nil
}
func resourceCloudStackFirewallDeleteRule(
d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
uuids := rule["uuids"].(map[string]interface{})
for k, id := range uuids {
// Create the parameter struct
p := cs.Firewall.NewDeleteFirewallRuleParams(id.(string))
// Delete the rule
if _, err := cs.Firewall.DeleteFirewallRule(p); err != nil {
// This is a very poor way to be told the UUID 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", id.(string))) {
delete(uuids, k)
continue
}
return err
}
// Delete the UUID of this rule
delete(uuids, k)
}
// Update the UUIDs
rule["uuids"] = uuids
return nil
}
func resourceCloudStackFirewallRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf(
"%s-%s-", m["source_cidr"].(string), m["protocol"].(string)))
if v, ok := m["icmp_type"]; ok {
buf.WriteString(fmt.Sprintf("%d-", v.(int)))
}
if v, ok := m["icmp_code"]; ok {
buf.WriteString(fmt.Sprintf("%d-", v.(int)))
}
// We need to make sure to sort the strings below so that we always
// generate the same hash code no matter what is in the set.
if v, ok := m["ports"]; ok {
vs := v.(*schema.Set).List()
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)
for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
return hashcode.String(buf.String())
}
func verifyFirewallParams(d *schema.ResourceData, rule map[string]interface{}) error {
protocol := rule["protocol"].(string)
if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
return fmt.Errorf(
"%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
}
if protocol == "icmp" {
if _, ok := rule["icmp_type"]; !ok {
return fmt.Errorf(
"Parameter icmp_type is a required parameter when using protocol 'icmp'")
}
if _, ok := rule["icmp_code"]; !ok {
return fmt.Errorf(
"Parameter icmp_code is a required parameter when using protocol 'icmp'")
}
} else {
if _, ok := rule["ports"]; !ok {
return fmt.Errorf(
"Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
}
}
return nil
}

View File

@ -0,0 +1,191 @@
package cloudstack
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackFirewall_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackFirewallDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackFirewall_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackFirewallRulesExist("cloudstack_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.1", "80"),
),
},
},
})
}
/*
func TestAccCloudStackFirewall_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackFirewallDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackFirewall_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackFirewallRulesExist("cloudstack_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.1", "80"),
),
},
resource.TestStep{
Config: testAccCloudStackFirewall_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackFirewallRulesExist("cloudstack_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.0", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.0.ports.1", "80"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.ports.0", "80"),
resource.TestCheckResourceAttr(
"cloudstack_firewall.foo", "rule.1.ports.1", "443"),
),
},
},
})
}
*/
func testAccCheckCloudStackFirewallRulesExist(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 firewall ID is set")
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
continue
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
_, count, err := cs.Firewall.GetFirewallRuleByID(uuid)
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("Firewall rule for %s not found", k)
}
}
return nil
}
}
func testAccCheckCloudStackFirewallDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_firewall" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
continue
}
p := cs.Firewall.NewDeleteFirewallRuleParams(uuid)
_, err := cs.Firewall.DeleteFirewallRule(p)
if err != nil {
return err
}
}
}
return nil
}
var testAccCloudStackFirewall_basic = fmt.Sprintf(`
resource "cloudstack_firewall" "foo" {
ipaddress = "%s"
rule {
source_cidr = "10.0.0.0/24"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
}`, CLOUDSTACK_PUBLIC_IPADDRESS)
var testAccCloudStackFirewall_update = fmt.Sprintf(`
resource "cloudstack_firewall" "foo" {
ipaddress = "%s"
rule {
source_cidr = "10.0.0.0/24"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
rule {
source_cidr = "172.16.100.0/24"
protocol = "tcp"
ports = ["80", "443"]
}
}`, CLOUDSTACK_PUBLIC_IPADDRESS)

View File

@ -0,0 +1,278 @@
package cloudstack
import (
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackInstance() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackInstanceCreate,
Read: resourceCloudStackInstanceRead,
Update: resourceCloudStackInstanceUpdate,
Delete: resourceCloudStackInstanceDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"display_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"service_offering": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"template": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"user_data": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
StateFunc: func(v interface{}) string {
switch v.(type) {
case string:
hash := sha1.Sum([]byte(v.(string)))
return hex.EncodeToString(hash[:])
default:
return ""
}
},
},
"expunge": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the service_offering UUID
serviceofferingid, e := retrieveUUID(cs, "service_offering", d.Get("service_offering").(string))
if e != nil {
return e.Error()
}
// Retrieve the template UUID
templateid, e := retrieveUUID(cs, "template", d.Get("template").(string))
if e != nil {
return e.Error()
}
// Retrieve the zone object
zone, _, err := cs.Zone.GetZoneByName(d.Get("zone").(string))
if err != nil {
return err
}
// Create a new parameter struct
p := cs.VirtualMachine.NewDeployVirtualMachineParams(serviceofferingid, templateid, zone.Id)
// Set the name
name := d.Get("name").(string)
p.SetName(name)
// Set the display name
if displayname, ok := d.GetOk("display_name"); ok {
p.SetDisplayname(displayname.(string))
} else {
p.SetDisplayname(name)
}
if zone.Networktype == "Advanced" {
// Retrieve the network UUID
networkid, e := retrieveUUID(cs, "network", d.Get("network").(string))
if e != nil {
return e.Error()
}
// Set the default network ID
p.SetNetworkids([]string{networkid})
}
// If there is a ipaddres supplied, add it to the parameter struct
if ipaddres, ok := d.GetOk("ipaddress"); ok {
p.SetIpaddress(ipaddres.(string))
}
// If the user data contains any info, it needs to be base64 encoded and
// added to the parameter struct
if userData, ok := d.GetOk("user_data"); ok {
ud := base64.StdEncoding.EncodeToString([]byte(userData.(string)))
if len(ud) > 2048 {
return fmt.Errorf(
"The supplied user_data contains %d bytes after encoding, "+
"this exeeds the limit of 2048 bytes", len(ud))
}
p.SetUserdata(ud)
}
// Create the new instance
r, err := cs.VirtualMachine.DeployVirtualMachine(p)
if err != nil {
return fmt.Errorf("Error creating the new instance %s: %s", name, err)
}
d.SetId(r.Id)
return resourceCloudStackInstanceRead(d, meta)
}
func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the virtual machine details
vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string))
// Clear out all details so it's obvious the instance is gone
d.SetId("")
return nil
}
return err
}
// Update the config
d.Set("name", vm.Name)
d.Set("display_name", vm.Displayname)
d.Set("service_offering", vm.Serviceofferingname)
d.Set("network", vm.Nic[0].Networkname)
d.Set("ipaddress", vm.Nic[0].Ipaddress)
d.Set("template", vm.Templatename)
d.Set("zone", vm.Zonename)
return nil
}
func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
d.Partial(true)
name := d.Get("name").(string)
// Check if the display name is changed and if so, update the virtual machine
if d.HasChange("display_name") {
log.Printf("[DEBUG] Display name changed for %s, starting update", name)
// Create a new parameter struct
p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
// Set the new display name
p.SetDisplayname(d.Get("display_name").(string))
// Update the display name
_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error updating the display name for instance %s: %s", name, err)
}
d.SetPartial("display_name")
}
// Check if the service offering is changed and if so, update the offering
if d.HasChange("service_offering") {
log.Printf("[DEBUG] Service offering changed for %s, starting update", name)
// Retrieve the service_offering UUID
serviceofferingid, e := retrieveUUID(cs, "service_offering", d.Get("service_offering").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid)
// Before we can actually change the service offering, the virtual machine must be stopped
_, err := cs.VirtualMachine.StopVirtualMachine(cs.VirtualMachine.NewStopVirtualMachineParams(d.Id()))
if err != nil {
return fmt.Errorf(
"Error stopping instance %s before changing service offering: %s", name, err)
}
// Change the service offering
_, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error changing the service offering for instance %s: %s", name, err)
}
// Start the virtual machine again
_, err = cs.VirtualMachine.StartVirtualMachine(cs.VirtualMachine.NewStartVirtualMachineParams(d.Id()))
if err != nil {
return fmt.Errorf(
"Error starting instance %s after changing service offering: %s", name, err)
}
d.SetPartial("service_offering")
}
d.Partial(false)
return resourceCloudStackInstanceRead(d, meta)
}
func resourceCloudStackInstanceDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VirtualMachine.NewDestroyVirtualMachineParams(d.Id())
if d.Get("expunge").(bool) {
p.SetExpunge(true)
}
log.Printf("[INFO] Destroying instance: %s", d.Get("name").(string))
if _, err := cs.VirtualMachine.DestroyVirtualMachine(p); err != nil {
// This is a very poor way to be told the UUID 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 destroying instance: %s", err)
}
return nil
}

View File

@ -0,0 +1,235 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackInstance_basic(t *testing.T) {
var instance cloudstack.VirtualMachine
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackInstance_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
testAccCheckCloudStackInstanceAttributes(&instance),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar",
"user_data",
"0cf3dcdc356ec8369494cb3991985ecd5296cdd5"),
),
},
},
})
}
func TestAccCloudStackInstance_update(t *testing.T) {
var instance cloudstack.VirtualMachine
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackInstance_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
testAccCheckCloudStackInstanceAttributes(&instance),
),
},
resource.TestStep{
Config: testAccCloudStackInstance_renameAndResize,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
testAccCheckCloudStackInstanceRenamedAndResized(&instance),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar", "display_name", "terraform-updated"),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar", "service_offering", CLOUDSTACK_SERVICE_OFFERING_2),
),
},
},
})
}
func TestAccCloudStackInstance_fixedIP(t *testing.T) {
var instance cloudstack.VirtualMachine
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackInstance_fixedIP,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar", "ipaddress", CLOUDSTACK_NETWORK_1_IPADDRESS),
),
},
},
})
}
func testAccCheckCloudStackInstanceExists(
n string, instance *cloudstack.VirtualMachine) 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 instance ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(rs.Primary.ID)
if err != nil {
return err
}
if vm.Id != rs.Primary.ID {
return fmt.Errorf("Instance not found")
}
*instance = *vm
return nil
}
}
func testAccCheckCloudStackInstanceAttributes(
instance *cloudstack.VirtualMachine) resource.TestCheckFunc {
return func(s *terraform.State) error {
if instance.Name != "terraform-test" {
return fmt.Errorf("Bad name: %s", instance.Name)
}
if instance.Displayname != "terraform" {
return fmt.Errorf("Bad display name: %s", instance.Displayname)
}
if instance.Serviceofferingname != CLOUDSTACK_SERVICE_OFFERING_1 {
return fmt.Errorf("Bad service offering: %s", instance.Serviceofferingname)
}
if instance.Templatename != CLOUDSTACK_TEMPLATE {
return fmt.Errorf("Bad template: %s", instance.Templatename)
}
if instance.Nic[0].Networkname != CLOUDSTACK_NETWORK_1 {
return fmt.Errorf("Bad network: %s", instance.Nic[0].Networkname)
}
return nil
}
}
func testAccCheckCloudStackInstanceRenamedAndResized(
instance *cloudstack.VirtualMachine) resource.TestCheckFunc {
return func(s *terraform.State) error {
if instance.Displayname != "terraform-updated" {
return fmt.Errorf("Bad display name: %s", instance.Displayname)
}
if instance.Serviceofferingname != CLOUDSTACK_SERVICE_OFFERING_2 {
return fmt.Errorf("Bad service offering: %s", instance.Serviceofferingname)
}
return nil
}
}
func testAccCheckCloudStackInstanceDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_instance" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}
p := cs.VirtualMachine.NewDestroyVirtualMachineParams(rs.Primary.ID)
err, _ := cs.VirtualMachine.DestroyVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error deleting instance (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackInstance_basic = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE)
var testAccCloudStackInstance_renameAndResize = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform-updated"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}`,
CLOUDSTACK_SERVICE_OFFERING_2,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE)
var testAccCloudStackInstance_fixedIP = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
ipaddress = "%s"
template = "%s"
zone = "%s"
expunge = true
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_NETWORK_1_IPADDRESS,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE)

View File

@ -0,0 +1,154 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackIPAddress() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackIPAddressCreate,
Read: resourceCloudStackIPAddressRead,
Delete: resourceCloudStackIPAddressDelete,
Schema: map[string]*schema.Schema{
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"vpc": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
if err := verifyIPAddressParams(d); err != nil {
return err
}
// Create a new parameter struct
p := cs.Address.NewAssociateIpAddressParams()
if network, ok := d.GetOk("network"); ok {
// Retrieve the network UUID
networkid, e := retrieveUUID(cs, "network", network.(string))
if e != nil {
return e.Error()
}
// Set the networkid
p.SetNetworkid(networkid)
}
if vpc, ok := d.GetOk("vpc"); ok {
// Retrieve the vpc UUID
vpcid, e := retrieveUUID(cs, "vpc", vpc.(string))
if e != nil {
return e.Error()
}
// Set the vpcid
p.SetVpcid(vpcid)
}
// Associate a new IP address
r, err := cs.Address.AssociateIpAddress(p)
if err != nil {
return fmt.Errorf("Error associating a new IP address: %s", err)
}
d.SetId(r.Id)
return resourceCloudStackIPAddressRead(d, meta)
}
func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the network ACL list details
f, count, err := cs.Address.GetPublicIpAddressByID(d.Id())
if err != nil {
if count == 0 {
log.Printf(
"[DEBUG] IP address with ID %s is no longer associated", d.Id())
d.SetId("")
return nil
}
return err
}
// Updated the IP address
d.Set("ipaddress", f.Ipaddress)
if _, ok := d.GetOk("network"); ok {
// Get the network details
n, _, err := cs.Network.GetNetworkByID(f.Associatednetworkid)
if err != nil {
return err
}
d.Set("network", n.Name)
}
if _, ok := d.GetOk("vpc"); ok {
// Get the VPC details
v, _, err := cs.VPC.GetVPCByID(f.Vpcid)
if err != nil {
return err
}
d.Set("vpc", v.Name)
}
return nil
}
func resourceCloudStackIPAddressDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.Address.NewDisassociateIpAddressParams(d.Id())
// Disassociate the IP address
if _, err := cs.Address.DisassociateIpAddress(p); err != nil {
// This is a very poor way to be told the UUID 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 network ACL list %s: %s", d.Get("name").(string), err)
}
return nil
}
func verifyIPAddressParams(d *schema.ResourceData) error {
_, network := d.GetOk("network")
_, vpc := d.GetOk("vpc")
if (network && vpc) || (!network && !vpc) {
return fmt.Errorf("You must supply a value for either (so not both) the 'network' or 'vpc' argument")
}
return nil
}

View File

@ -0,0 +1,137 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackIPAddress_basic(t *testing.T) {
var ipaddr cloudstack.PublicIpAddress
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackIPAddressDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackIPAddress_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackIPAddressExists(
"cloudstack_ipaddress.foo", &ipaddr),
testAccCheckCloudStackIPAddressAttributes(&ipaddr),
),
},
},
})
}
func TestAccCloudStackIPAddress_vpc(t *testing.T) {
var ipaddr cloudstack.PublicIpAddress
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackIPAddressDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackIPAddress_vpc,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackIPAddressExists(
"cloudstack_ipaddress.foo", &ipaddr),
resource.TestCheckResourceAttr(
"cloudstack_ipaddress.foo", "vpc", "terraform-vpc"),
),
},
},
})
}
func testAccCheckCloudStackIPAddressExists(
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 IP address ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
pip, _, err := cs.Address.GetPublicIpAddressByID(rs.Primary.ID)
if err != nil {
return err
}
if pip.Id != rs.Primary.ID {
return fmt.Errorf("IP address not found")
}
*ipaddr = *pip
return nil
}
}
func testAccCheckCloudStackIPAddressAttributes(
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)
}
return nil
}
}
func testAccCheckCloudStackIPAddressDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_ipaddress" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No IP address ID is set")
}
p := cs.Address.NewDisassociateIpAddressParams(rs.Primary.ID)
err, _ := cs.Address.DisassociateIpAddress(p)
if err != nil {
return fmt.Errorf(
"Error disassociating IP address (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackIPAddress_basic = fmt.Sprintf(`
resource "cloudstack_ipaddress" "foo" {
network = "%s"
}`, CLOUDSTACK_NETWORK_1)
var testAccCloudStackIPAddress_vpc = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_ipaddress" "foo" {
vpc = "${cloudstack_vpc.foobar.name}"
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)

View File

@ -0,0 +1,241 @@
package cloudstack
import (
"fmt"
"log"
"net"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackNetwork() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackNetworkCreate,
Read: resourceCloudStackNetworkRead,
Update: resourceCloudStackNetworkUpdate,
Delete: resourceCloudStackNetworkDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"display_text": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"network_offering": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"vpc": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"aclid": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
name := d.Get("name").(string)
// Retrieve the network_offering UUID
networkofferingid, e := retrieveUUID(cs, "network_offering", d.Get("network_offering").(string))
if e != nil {
return e.Error()
}
// Retrieve the zone UUID
zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
if e != nil {
return e.Error()
}
// Compute/set the display text
displaytext := d.Get("display_text").(string)
if displaytext == "" {
displaytext = name
}
// Create a new parameter struct
p := cs.Network.NewCreateNetworkParams(displaytext, name, networkofferingid, zoneid)
// Get the network details from the CIDR
m, err := parseCIDR(d.Get("cidr").(string))
if err != nil {
return err
}
// Set the needed IP config
p.SetStartip(m["start"])
p.SetGateway(m["gateway"])
p.SetEndip(m["end"])
p.SetNetmask(m["netmask"])
// Check is this network needs to be created in a VPC
vpc := d.Get("vpc").(string)
if vpc != "" {
// Retrieve the vpc UUID
vpcid, e := retrieveUUID(cs, "vpc", vpc)
if e != nil {
return e.Error()
}
// Set the vpc UUID
p.SetVpcid(vpcid)
// Since we're in a VPC, check if we want to assiciate an ACL list
aclid := d.Get("aclid").(string)
if aclid != "" {
// Set the acl UUID
p.SetAclid(aclid)
}
}
// Create the new network
r, err := cs.Network.CreateNetwork(p)
if err != nil {
return fmt.Errorf("Error creating network %s: %s", name, err)
}
d.SetId(r.Id)
return resourceCloudStackNetworkRead(d, meta)
}
func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the virtual machine details
n, count, err := cs.Network.GetNetworkByID(d.Id())
if err != nil {
if count == 0 {
log.Printf(
"[DEBUG] Network %s does no longer exist", d.Get("name").(string))
d.SetId("")
return nil
}
return err
}
d.Set("name", n.Name)
d.Set("display_test", n.Displaytext)
d.Set("cidr", n.Cidr)
d.Set("network_offering", n.Networkofferingname)
d.Set("zone", n.Zonename)
return nil
}
func resourceCloudStackNetworkUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
name := d.Get("name").(string)
// Create a new parameter struct
p := cs.Network.NewUpdateNetworkParams(d.Id())
// Check if the name or display text is changed
if d.HasChange("name") || d.HasChange("display_text") {
p.SetName(name)
// Compute/set the display text
displaytext := d.Get("display_text").(string)
if displaytext == "" {
displaytext = name
}
}
// Check if the cidr is changed
if d.HasChange("cidr") {
p.SetGuestvmcidr(d.Get("cidr").(string))
}
// Check if the network offering is changed
if d.HasChange("network_offering") {
// Retrieve the network_offering UUID
networkofferingid, e := retrieveUUID(cs, "network_offering", d.Get("network_offering").(string))
if e != nil {
return e.Error()
}
// Set the new network offering
p.SetNetworkofferingid(networkofferingid)
}
// Update the network
_, err := cs.Network.UpdateNetwork(p)
if err != nil {
return fmt.Errorf(
"Error updating network %s: %s", name, err)
}
return resourceCloudStackNetworkRead(d, meta)
}
func resourceCloudStackNetworkDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.Network.NewDeleteNetworkParams(d.Id())
// Delete the network
_, err := cs.Network.DeleteNetwork(p)
if err != nil {
// This is a very poor way to be told the UUID 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 network %s: %s", d.Get("name").(string), err)
}
return nil
}
func parseCIDR(cidr string) (map[string]string, error) {
m := make(map[string]string, 4)
ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, fmt.Errorf("Unable to parse cidr %s: %s", cidr, err)
}
msk := ipnet.Mask
sub := ip.Mask(msk)
m["netmask"] = fmt.Sprintf("%d.%d.%d.%d", msk[0], msk[1], msk[2], msk[3])
m["gateway"] = fmt.Sprintf("%d.%d.%d.%d", sub[0], sub[1], sub[2], sub[3]+1)
m["start"] = fmt.Sprintf("%d.%d.%d.%d", sub[0], sub[1], sub[2], sub[3]+2)
m["end"] = fmt.Sprintf("%d.%d.%d.%d",
sub[0]+(0xff-msk[0]), sub[1]+(0xff-msk[1]), sub[2]+(0xff-msk[2]), sub[3]+(0xff-msk[3]-1))
return m, nil
}

View File

@ -0,0 +1,123 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackNetworkACL() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackNetworkACLCreate,
Read: resourceCloudStackNetworkACLRead,
Delete: resourceCloudStackNetworkACLDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"vpc": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackNetworkACLCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
name := d.Get("name").(string)
// Retrieve the vpc UUID
vpcid, e := retrieveUUID(cs, "vpc", d.Get("vpc").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.NetworkACL.NewCreateNetworkACLListParams(name, vpcid)
// Set the description
if description, ok := d.GetOk("description"); ok {
p.SetDescription(description.(string))
} else {
p.SetDescription(name)
}
// Create the new network ACL list
r, err := cs.NetworkACL.CreateNetworkACLList(p)
if err != nil {
return fmt.Errorf("Error creating network ACL list %s: %s", name, err)
}
d.SetId(r.Id)
return resourceCloudStackNetworkACLRead(d, meta)
}
func resourceCloudStackNetworkACLRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the network ACL list details
f, count, err := cs.NetworkACL.GetNetworkACLListByID(d.Id())
if err != nil {
if count == 0 {
log.Printf(
"[DEBUG] Network ACL list %s does no longer exist", d.Get("name").(string))
d.SetId("")
return nil
}
return err
}
d.Set("name", f.Name)
d.Set("description", f.Description)
// Get the VPC details
v, _, err := cs.VPC.GetVPCByID(f.Vpcid)
if err != nil {
return err
}
d.Set("vpc", v.Name)
return nil
}
func resourceCloudStackNetworkACLDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.NetworkACL.NewDeleteNetworkACLListParams(d.Id())
// Delete the network ACL list
_, err := cs.NetworkACL.DeleteNetworkACLList(p)
if err != nil {
// This is a very poor way to be told the UUID 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 network ACL list %s: %s", d.Get("name").(string), err)
}
return nil
}

View File

@ -0,0 +1,476 @@
package cloudstack
import (
"bytes"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackNetworkACLRule() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackNetworkACLRuleCreate,
Read: resourceCloudStackNetworkACLRuleRead,
Update: resourceCloudStackNetworkACLRuleUpdate,
Delete: resourceCloudStackNetworkACLRuleDelete,
Schema: map[string]*schema.Schema{
"aclid": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"rule": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"action": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "allow",
},
"source_cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"icmp_type": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"icmp_code": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"ports": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
"traffic_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ingress",
},
"uuids": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
},
},
},
Set: resourceCloudStackNetworkACLRuleHash,
},
},
}
}
func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error {
// Get the acl UUID
aclid := d.Get("aclid").(string)
// We need to set this upfront in order to be able to save a partial state
d.SetId(aclid)
// Create all rules that are configured
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceCloudStackNetworkACLRuleHash,
}
for _, rule := range rs.List() {
// Create a single rule
err := resourceCloudStackNetworkACLRuleCreateRule(
d, meta, aclid, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
d.Set("rule", rules)
if err != nil {
return err
}
}
}
return resourceCloudStackNetworkACLRuleRead(d, meta)
}
func resourceCloudStackNetworkACLRuleCreateRule(
d *schema.ResourceData, meta interface{}, aclid string, rule map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
uuids := rule["uuids"].(map[string]interface{})
// Make sure all required parameters are there
if err := verifyNetworkACLRuleParams(d, rule); err != nil {
return err
}
// Create a new parameter struct
p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string))
// Set the acl ID
p.SetAclid(aclid)
// Set the action
p.SetAction(rule["action"].(string))
// Set the CIDR list
p.SetCidrlist([]string{rule["source_cidr"].(string)})
// Set the traffic type
p.SetTraffictype(rule["traffic_type"].(string))
// If the protocol is ICMP set the needed ICMP parameters
if rule["protocol"].(string) == "icmp" {
p.SetIcmptype(rule["icmp_type"].(int))
p.SetIcmpcode(rule["icmp_code"].(int))
r, err := cs.NetworkACL.CreateNetworkACL(p)
if err != nil {
return err
}
uuids["icmp"] = r.Id
rule["uuids"] = uuids
}
// If protocol is not ICMP, loop through all ports
if rule["protocol"].(string) != "icmp" {
if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
// Create an empty schema.Set to hold all processed ports
ports := &schema.Set{
F: func(v interface{}) int {
return hashcode.String(v.(string))
},
}
for _, port := range ps.List() {
re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
m := re.FindStringSubmatch(port.(string))
startPort, err := strconv.Atoi(m[1])
if err != nil {
return err
}
endPort := startPort
if m[2] != "" {
endPort, err = strconv.Atoi(m[2])
if err != nil {
return err
}
}
p.SetStartport(startPort)
p.SetEndport(endPort)
r, err := cs.NetworkACL.CreateNetworkACL(p)
if err != nil {
return err
}
ports.Add(port)
rule["ports"] = ports
uuids[port.(string)] = r.Id
rule["uuids"] = uuids
}
}
}
return nil
}
func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceCloudStackNetworkACLRuleHash,
}
// Read all rules that are configured
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
for _, rule := range rs.List() {
rule := rule.(map[string]interface{})
uuids := rule["uuids"].(map[string]interface{})
if rule["protocol"].(string) == "icmp" {
id, ok := uuids["icmp"]
if !ok {
continue
}
// Get the rule
r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
// If the count == 0, there is no object found for this UUID
if err != nil {
if count == 0 {
delete(uuids, "icmp")
continue
}
return err
}
// Update the values
rule["action"] = r.Action
rule["source_cidr"] = r.Cidrlist
rule["protocol"] = r.Protocol
rule["icmp_type"] = r.Icmptype
rule["icmp_code"] = r.Icmpcode
rule["traffic_type"] = r.Traffictype
rules.Add(rule)
}
// If protocol is not ICMP, loop through all ports
if rule["protocol"].(string) != "icmp" {
if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
// Create an empty schema.Set to hold all ports
ports := &schema.Set{
F: func(v interface{}) int {
return hashcode.String(v.(string))
},
}
// Loop through all ports and retrieve their info
for _, port := range ps.List() {
id, ok := uuids[port.(string)]
if !ok {
continue
}
// Get the rule
r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
if err != nil {
if count == 0 {
delete(uuids, port.(string))
continue
}
return err
}
// Update the values
rule["action"] = strings.ToLower(r.Action)
rule["source_cidr"] = r.Cidrlist
rule["protocol"] = r.Protocol
rule["traffic_type"] = strings.ToLower(r.Traffictype)
ports.Add(port)
}
// If there is at least one port found, add this rule to the rules set
if ports.Len() > 0 {
rule["ports"] = ports
rules.Add(rule)
}
}
}
}
}
if rules.Len() > 0 {
d.Set("rule", rules)
} else {
d.SetId("")
}
return nil
}
func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error {
// Get the acl UUID
aclid := d.Get("aclid").(string)
// Check if the rule set as a whole has changed
if d.HasChange("rule") {
o, n := d.GetChange("rule")
ors := o.(*schema.Set).Difference(n.(*schema.Set))
nrs := n.(*schema.Set).Difference(o.(*schema.Set))
// Now first loop through all the old rules and delete any obsolete ones
for _, rule := range ors.List() {
// Delete the rule as it no longer exists in the config
err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
if err != nil {
return err
}
}
// Make sure we save the state of the currently configured rules
rules := o.(*schema.Set).Intersection(n.(*schema.Set))
d.Set("rule", rules)
// Then loop through al the currently configured rules and create the new ones
for _, rule := range nrs.List() {
// When succesfully deleted, re-create it again if it still exists
err := resourceCloudStackNetworkACLRuleCreateRule(
d, meta, aclid, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
d.Set("rule", rules)
if err != nil {
return err
}
}
}
return resourceCloudStackNetworkACLRuleRead(d, meta)
}
func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error {
// Delete all rules
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
for _, rule := range rs.List() {
// Delete a single rule
err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
d.Set("rule", rs)
if err != nil {
return err
}
}
}
return nil
}
func resourceCloudStackNetworkACLRuleDeleteRule(
d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
uuids := rule["uuids"].(map[string]interface{})
for k, id := range uuids {
// Create the parameter struct
p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string))
// Delete the rule
if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil {
// This is a very poor way to be told the UUID 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", id.(string))) {
delete(uuids, k)
continue
}
return err
}
// Delete the UUID of this rule
delete(uuids, k)
}
// Update the UUIDs
rule["uuids"] = uuids
return nil
}
func resourceCloudStackNetworkACLRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf(
"%s-%s-%s-%s-",
m["action"].(string),
m["source_cidr"].(string),
m["protocol"].(string),
m["traffic_type"].(string)))
if v, ok := m["icmp_type"]; ok {
buf.WriteString(fmt.Sprintf("%d-", v.(int)))
}
if v, ok := m["icmp_code"]; ok {
buf.WriteString(fmt.Sprintf("%d-", v.(int)))
}
// We need to make sure to sort the strings below so that we always
// generate the same hash code no matter what is in the set.
if v, ok := m["ports"]; ok {
vs := v.(*schema.Set).List()
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)
for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
return hashcode.String(buf.String())
}
func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
action := rule["action"].(string)
if action != "allow" && action != "deny" {
return fmt.Errorf("Parameter action only excepts 'allow' or 'deny' as values")
}
protocol := rule["protocol"].(string)
if protocol == "icmp" {
if _, ok := rule["icmp_type"]; !ok {
return fmt.Errorf(
"Parameter icmp_type is a required parameter when using protocol 'icmp'")
}
if _, ok := rule["icmp_code"]; !ok {
return fmt.Errorf(
"Parameter icmp_code is a required parameter when using protocol 'icmp'")
}
} else {
if protocol != "tcp" && protocol != "udp" && protocol != "all" {
_, err := strconv.ParseInt(protocol, 0, 0)
if err != nil {
return fmt.Errorf(
"%s is not a valid protocol. Valid options are 'tcp', 'udp', "+
"'icmp', 'all' or a valid protocol number", protocol)
}
}
if _, ok := rule["ports"]; !ok {
return fmt.Errorf(
"Parameter ports is a required parameter when *not* using protocol 'icmp'")
}
}
traffic := rule["traffic_type"].(string)
if traffic != "ingress" && traffic != "egress" {
return fmt.Errorf(
"Parameter traffic_type only excepts 'ingress' or 'egress' as values")
}
return nil
}

View File

@ -0,0 +1,241 @@
package cloudstack
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackNetworkACLRule_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNetworkACLRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNetworkACLRule_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNetworkACLRulesExist("cloudstack_network_acl.foo"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.action", "allow"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.0", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.1", "443"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.traffic_type", "ingress"),
),
},
},
})
}
/*
func TestAccCloudStackNetworkACLRule_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNetworkACLRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNetworkACLRule_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNetworkACLRulesExist("cloudstack_network_acl.foo"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.action", "allow"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.0", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.1", "443"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.traffic_type", "ingress"),
),
},
resource.TestStep{
Config: testAccCloudStackNetworkACLRule_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNetworkACLRulesExist("cloudstack_network_acl.foo"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.action", "allow"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.0", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.ports.1", "443"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.0.traffic_type", "ingress"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.action", "deny"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.ports.0", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.ports.1", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.1.traffic_type", "engress"),
),
},
},
})
}
*/
func testAccCheckCloudStackNetworkACLRulesExist(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 network ACL rule ID is set")
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
continue
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
_, count, err := cs.NetworkACL.GetNetworkACLByID(uuid)
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("Network ACL rule %s not found", k)
}
}
return nil
}
}
func testAccCheckCloudStackNetworkACLRuleDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_network_acl_rule" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No network ACL rule ID is set")
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
continue
}
p := cs.NetworkACL.NewDeleteNetworkACLParams(uuid)
_, err := cs.NetworkACL.DeleteNetworkACL(p)
if err != nil {
return err
}
}
}
return nil
}
var testAccCloudStackNetworkACLRule_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_network_acl" "foo" {
name = "terraform-acl"
description = "terraform-acl-text"
vpc = "${cloudstack_vpc.foobar.name}"
}
resource "cloudstack_network_acl_rule" "foo" {
aclid = "${cloudstack_network_acl.foo.id}"
rule {
action = "allow"
source_cidr = "172.16.100.0/24"
protocol = "tcp"
ports = ["80", "443"]
traffic_type = "ingress"
}
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)
var testAccCloudStackNetworkACLRule_update = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_network_acl" "foo" {
name = "terraform-acl"
description = "terraform-acl-text"
vpc = "${cloudstack_vpc.foobar.name}"
}
resource "cloudstack_network_acl_rule" "foo" {
aclid = "${cloudstack_network_acl.foo.id}"
rule {
action = "allow"
source_cidr = "172.16.100.0/24"
protocol = "tcp"
ports = ["80", "443"]
traffic_type = "ingress"
}
rule {
action = "deny"
source_cidr = "10.0.0.0/24"
protocol = "tcp"
ports = ["80", "1000-2000"]
traffic_type = "egress"
}
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)

View File

@ -0,0 +1,117 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackNetworkACL_basic(t *testing.T) {
var acl cloudstack.NetworkACLList
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNetworkACLDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNetworkACL_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNetworkACLExists(
"cloudstack_network_acl.foo", &acl),
testAccCheckCloudStackNetworkACLBasicAttributes(&acl),
resource.TestCheckResourceAttr(
"cloudstack_network_acl.foo", "vpc", "terraform-vpc"),
),
},
},
})
}
func testAccCheckCloudStackNetworkACLExists(
n string, acl *cloudstack.NetworkACLList) 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 network ACL ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
acllist, _, err := cs.NetworkACL.GetNetworkACLListByID(rs.Primary.ID)
if err != nil {
return err
}
if acllist.Id != rs.Primary.ID {
return fmt.Errorf("Network ACL not found")
}
*acl = *acllist
return nil
}
}
func testAccCheckCloudStackNetworkACLBasicAttributes(
acl *cloudstack.NetworkACLList) resource.TestCheckFunc {
return func(s *terraform.State) error {
if acl.Name != "terraform-acl" {
return fmt.Errorf("Bad name: %s", acl.Name)
}
if acl.Description != "terraform-acl-text" {
return fmt.Errorf("Bad description: %s", acl.Description)
}
return nil
}
}
func testAccCheckCloudStackNetworkACLDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_network_acl" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No network ACL ID is set")
}
p := cs.NetworkACL.NewDeleteNetworkACLListParams(rs.Primary.ID)
err, _ := cs.NetworkACL.DeleteNetworkACLList(p)
if err != nil {
return fmt.Errorf(
"Error deleting network ACL (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackNetworkACL_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_network_acl" "foo" {
name = "terraform-acl"
description = "terraform-acl-text"
vpc = "${cloudstack_vpc.foobar.name}"
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)

View File

@ -0,0 +1,193 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackNetwork_basic(t *testing.T) {
var network cloudstack.Network
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNetworkDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNetwork_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNetworkExists(
"cloudstack_network.foo", &network),
testAccCheckCloudStackNetworkBasicAttributes(&network),
),
},
},
})
}
func TestAccCloudStackNetwork_vpcACL(t *testing.T) {
var network cloudstack.Network
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNetworkDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNetwork_vpcACL,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNetworkExists(
"cloudstack_network.foo", &network),
testAccCheckCloudStackNetworkVPCACLAttributes(&network),
resource.TestCheckResourceAttr(
"cloudstack_network.foo", "vpc", "terraform-vpc"),
),
},
},
})
}
func testAccCheckCloudStackNetworkExists(
n string, network *cloudstack.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 network ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
ntwrk, _, err := cs.Network.GetNetworkByID(rs.Primary.ID)
if err != nil {
return err
}
if ntwrk.Id != rs.Primary.ID {
return fmt.Errorf("Network not found")
}
*network = *ntwrk
return nil
}
}
func testAccCheckCloudStackNetworkBasicAttributes(
network *cloudstack.Network) resource.TestCheckFunc {
return func(s *terraform.State) error {
if network.Name != "terraform-network" {
return fmt.Errorf("Bad name: %s", network.Name)
}
if network.Displaytext != "terraform-network" {
return fmt.Errorf("Bad display name: %s", network.Displaytext)
}
if network.Cidr != CLOUDSTACK_NETWORK_1_CIDR {
return fmt.Errorf("Bad service offering: %s", network.Cidr)
}
if network.Networkofferingname != CLOUDSTACK_NETWORK_1_OFFERING {
return fmt.Errorf("Bad template: %s", network.Networkofferingname)
}
return nil
}
}
func testAccCheckCloudStackNetworkVPCACLAttributes(
network *cloudstack.Network) resource.TestCheckFunc {
return func(s *terraform.State) error {
if network.Name != "terraform-network" {
return fmt.Errorf("Bad name: %s", network.Name)
}
if network.Displaytext != "terraform-network" {
return fmt.Errorf("Bad display name: %s", network.Displaytext)
}
if network.Cidr != CLOUDSTACK_VPC_NETWORK_CIDR {
return fmt.Errorf("Bad service offering: %s", network.Cidr)
}
if network.Networkofferingname != CLOUDSTACK_VPC_NETWORK_OFFERING {
return fmt.Errorf("Bad template: %s", network.Networkofferingname)
}
return nil
}
}
func testAccCheckCloudStackNetworkDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_network" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No network ID is set")
}
p := cs.Network.NewDeleteNetworkParams(rs.Primary.ID)
err, _ := cs.Network.DeleteNetwork(p)
if err != nil {
return fmt.Errorf(
"Error deleting network (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackNetwork_basic = fmt.Sprintf(`
resource "cloudstack_network" "foo" {
name = "terraform-network"
cidr = "%s"
network_offering = "%s"
zone = "%s"
}`,
CLOUDSTACK_NETWORK_1_CIDR,
CLOUDSTACK_NETWORK_1_OFFERING,
CLOUDSTACK_ZONE)
var testAccCloudStackNetwork_vpcACL = fmt.Sprintf(`
resource "cloudstack_vpc" "foobar" {
name = "terraform-vpc"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}
resource "cloudstack_network_acl" "foo" {
name = "terraform-acl"
description = "terraform-acl-text"
vpc = "${cloudstack_vpc.foobar.name}"
}
resource "cloudstack_network" "foo" {
name = "terraform-network"
cidr = "%s"
network_offering = "%s"
vpc = "${cloudstack_vpc.foobar.name}"
aclid = "${cloudstack_network_acl.foo.id}"
zone = "${cloudstack_vpc.foobar.zone}"
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE,
CLOUDSTACK_VPC_NETWORK_CIDR,
CLOUDSTACK_VPC_NETWORK_OFFERING)

View File

@ -0,0 +1,147 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackNIC() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackNICCreate,
Read: resourceCloudStackNICRead,
Delete: resourceCloudStackNICDelete,
Schema: map[string]*schema.Schema{
"network": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"virtual_machine": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackNICCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the network UUID
networkid, e := retrieveUUID(cs, "network", d.Get("network").(string))
if e != nil {
return e.Error()
}
// Retrieve the virtual_machine UUID
virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.VirtualMachine.NewAddNicToVirtualMachineParams(networkid, virtualmachineid)
// If there is a ipaddres supplied, add it to the parameter struct
if ipaddress, ok := d.GetOk("ipaddress"); ok {
p.SetIpaddress(ipaddress.(string))
}
// Create and attach the new NIC
r, err := cs.VirtualMachine.AddNicToVirtualMachine(p)
if err != nil {
return fmt.Errorf("Error creating the new NIC: %s", err)
}
found := false
for _, n := range r.Nic {
if n.Networkid == networkid {
d.SetId(n.Id)
found = true
break
}
}
if !found {
return fmt.Errorf("Could not find NIC ID for network: %s", d.Get("network").(string))
}
return resourceCloudStackNICRead(d, meta)
}
func resourceCloudStackNICRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the virtual machine details
vm, count, err := cs.VirtualMachine.GetVirtualMachineByName(d.Get("virtual_machine").(string))
if err != nil {
if count == 0 {
log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("virtual_machine").(string))
d.SetId("")
return nil
} else {
return err
}
}
// Read NIC info
found := false
for _, n := range vm.Nic {
if n.Id == d.Id() {
d.Set("network", n.Networkname)
d.Set("ipaddress", n.Ipaddress)
d.Set("virtual_machine", vm.Name)
found = true
break
}
}
if !found {
log.Printf("[DEBUG] NIC for network %s does no longer exist", d.Get("network").(string))
d.SetId("")
}
return nil
}
func resourceCloudStackNICDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the virtual_machine UUID
virtualmachineid, e := retrieveUUID(cs, "virtual_machine", d.Get("virtual_machine").(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.VirtualMachine.NewRemoveNicFromVirtualMachineParams(d.Id(), virtualmachineid)
// Remove the NIC
_, err := cs.VirtualMachine.RemoveNicFromVirtualMachine(p)
if err != nil {
// This is a very poor way to be told the UUID 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 NIC: %s", err)
}
return nil
}

View File

@ -0,0 +1,198 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackNIC_basic(t *testing.T) {
var nic cloudstack.Nic
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNICDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNIC_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNICExists(
"cloudstack_instance.foobar", "cloudstack_nic.foo", &nic),
testAccCheckCloudStackNICAttributes(&nic),
),
},
},
})
}
func TestAccCloudStackNIC_update(t *testing.T) {
var nic cloudstack.Nic
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackNICDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackNIC_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNICExists(
"cloudstack_instance.foobar", "cloudstack_nic.foo", &nic),
testAccCheckCloudStackNICAttributes(&nic),
),
},
resource.TestStep{
Config: testAccCloudStackNIC_ipaddress,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackNICExists(
"cloudstack_instance.foobar", "cloudstack_nic.foo", &nic),
testAccCheckCloudStackNICIPAddress(&nic),
resource.TestCheckResourceAttr(
"cloudstack_nic.foo", "ipaddress", CLOUDSTACK_NETWORK_2_IPADDRESS),
),
},
},
})
}
func testAccCheckCloudStackNICExists(
v, n string, nic *cloudstack.Nic) resource.TestCheckFunc {
return func(s *terraform.State) error {
rsv, ok := s.RootModule().Resources[v]
if !ok {
return fmt.Errorf("Not found: %s", v)
}
if rsv.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}
rsn, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rsn.Primary.ID == "" {
return fmt.Errorf("No NIC ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(rsv.Primary.ID)
if err != nil {
return err
}
for _, n := range vm.Nic {
if n.Id == rsn.Primary.ID {
*nic = n
return nil
}
}
return fmt.Errorf("NIC not found")
}
}
func testAccCheckCloudStackNICAttributes(
nic *cloudstack.Nic) resource.TestCheckFunc {
return func(s *terraform.State) error {
if nic.Networkname != CLOUDSTACK_NETWORK_2 {
return fmt.Errorf("Bad network: %s", nic.Networkname)
}
return nil
}
}
func testAccCheckCloudStackNICIPAddress(
nic *cloudstack.Nic) resource.TestCheckFunc {
return func(s *terraform.State) error {
if nic.Networkname != CLOUDSTACK_NETWORK_2 {
return fmt.Errorf("Bad network: %s", nic.Networkname)
}
if nic.Ipaddress != CLOUDSTACK_NETWORK_2_IPADDRESS {
return fmt.Errorf("Bad IP address: %s", nic.Ipaddress)
}
return nil
}
}
func testAccCheckCloudStackNICDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
// Deleting the instance automatically deletes any additional NICs
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_instance" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}
p := cs.VirtualMachine.NewDestroyVirtualMachineParams(rs.Primary.ID)
err, _ := cs.VirtualMachine.DestroyVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error deleting instance (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackNIC_basic = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
expunge = true
}
resource "cloudstack_nic" "foo" {
network = "%s"
virtual_machine = "${cloudstack_instance.foobar.name}"
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_NETWORK_2)
var testAccCloudStackNIC_ipaddress = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
expunge = true
}
resource "cloudstack_nic" "foo" {
network = "%s"
ipaddress = "%s"
virtual_machine = "${cloudstack_instance.foobar.name}"
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_NETWORK_2,
CLOUDSTACK_NETWORK_2_IPADDRESS)

View File

@ -0,0 +1,299 @@
package cloudstack
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackPortForward() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackPortForwardCreate,
Read: resourceCloudStackPortForwardRead,
Update: resourceCloudStackPortForwardUpdate,
Delete: resourceCloudStackPortForwardDelete,
Schema: map[string]*schema.Schema{
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"forward": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"private_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"public_port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"virtual_machine": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"uuid": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
Set: resourceCloudStackPortForwardHash,
},
},
}
}
func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the ipaddress UUID
ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
if e != nil {
return e.Error()
}
// We need to set this upfront in order to be able to save a partial state
d.SetId(d.Get("ipaddress").(string))
// Create all forwards that are configured
if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
// Create an empty schema.Set to hold all forwards
forwards := &schema.Set{
F: resourceCloudStackPortForwardHash,
}
for _, forward := range rs.List() {
// Create a single forward
err := resourceCloudStackPortForwardCreateForward(d, meta, ipaddressid, forward.(map[string]interface{}))
// We need to update this first to preserve the correct state
forwards.Add(forward)
d.Set("forward", forwards)
if err != nil {
return err
}
}
}
return resourceCloudStackPortForwardRead(d, meta)
}
func resourceCloudStackPortForwardCreateForward(
d *schema.ResourceData, meta interface{}, ipaddressid string, forward map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Make sure all required parameters are there
if err := verifyPortForwardParams(d, forward); err != nil {
return err
}
// Retrieve the virtual_machine UUID
virtualmachineid, e := retrieveUUID(cs, "virtual_machine", forward["virtual_machine"].(string))
if e != nil {
return e.Error()
}
// Create a new parameter struct
p := cs.Firewall.NewCreatePortForwardingRuleParams(ipaddressid, forward["private_port"].(int),
forward["protocol"].(string), forward["public_port"].(int), virtualmachineid)
// Do not open the firewall automatically in any case
p.SetOpenfirewall(false)
r, err := cs.Firewall.CreatePortForwardingRule(p)
if err != nil {
return err
}
forward["uuid"] = r.Id
return nil
}
func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create an empty schema.Set to hold all forwards
forwards := &schema.Set{
F: resourceCloudStackPortForwardHash,
}
// Read all forwards that are configured
if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
for _, forward := range rs.List() {
forward := forward.(map[string]interface{})
id, ok := forward["uuid"]
if !ok || id.(string) == "" {
continue
}
// Get the forward
r, count, err := cs.Firewall.GetPortForwardingRuleByID(id.(string))
// If the count == 0, there is no object found for this UUID
if err != nil {
if count != 0 {
continue
}
return err
}
privPort, err := strconv.Atoi(r.Privateport)
if err != nil {
return fmt.Errorf("Error converting private_port: %s", err)
}
pubPort, err := strconv.Atoi(r.Publicport)
if err != nil {
return fmt.Errorf("Error converting public_port: %s", err)
}
// Update the values
forward["protocol"] = r.Protocol
forward["private_port"] = privPort
forward["public_port"] = pubPort
forward["virtual_machine"] = r.Virtualmachinename
forwards.Add(forward)
}
}
if forwards.Len() > 0 {
d.Set("forward", forwards)
} else {
d.SetId("")
}
return nil
}
func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Retrieve the ipaddress UUID
ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
if e != nil {
return e.Error()
}
// Check if the forward set as a whole has changed
if d.HasChange("forward") {
o, n := d.GetChange("forward")
ors := o.(*schema.Set).Difference(n.(*schema.Set))
nrs := n.(*schema.Set).Difference(o.(*schema.Set))
// Now first loop through all the old forwards and delete any obsolete ones
for _, forward := range ors.List() {
// Delete the forward as it no longer exists in the config
err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
if err != nil {
return err
}
}
// Make sure we save the state of the currently configured forwards
forwards := o.(*schema.Set).Intersection(n.(*schema.Set))
d.Set("forward", forwards)
// Then loop through al the currently configured forwards and create the new ones
for _, forward := range nrs.List() {
err := resourceCloudStackPortForwardCreateForward(
d, meta, ipaddressid, forward.(map[string]interface{}))
// We need to update this first to preserve the correct state
forwards.Add(forward)
d.Set("forward", forwards)
if err != nil {
return err
}
}
}
return resourceCloudStackPortForwardRead(d, meta)
}
func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error {
// Delete all forwards
if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
for _, forward := range rs.List() {
// Delete a single forward
err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
// We need to update this first to preserve the correct state
d.Set("forward", rs)
if err != nil {
return err
}
}
}
return nil
}
func resourceCloudStackPortForwardDeleteForward(
d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create the parameter struct
p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string))
// Delete the forward
if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil {
// This is a very poor way to be told the UUID 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", forward["uuid"].(string))) {
return err
}
}
forward["uuid"] = ""
return nil
}
func resourceCloudStackPortForwardHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf(
"%s-%d-%d-%s",
m["protocol"].(string),
m["private_port"].(int),
m["public_port"].(int),
m["virtual_machine"].(string)))
return hashcode.String(buf.String())
}
func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error {
protocol := forward["protocol"].(string)
if protocol != "tcp" && protocol != "udp" {
return fmt.Errorf(
"%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol)
}
return nil
}

View File

@ -0,0 +1,219 @@
package cloudstack
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackPortForward_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackPortForwardDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackPortForward_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.virtual_machine", "terraform-test"),
),
},
},
})
}
/*
func TestAccCloudStackPortForward_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackPortForwardDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackPortForward_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.virtual_machine", "terraform-test"),
),
},
resource.TestStep{
Config: testAccCloudStackPortForward_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "ipaddress", CLOUDSTACK_PUBLIC_IPADDRESS),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "80"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8080"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.virtual_machine", "terraform-test"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.public_port", "8443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.virtual_machine", "terraform-test"),
),
},
},
})
}
*/
func testAccCheckCloudStackPortForwardsExist(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 port forward ID is set")
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuid") {
continue
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
_, count, err := cs.Firewall.GetPortForwardingRuleByID(uuid)
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("Port forward for %s not found", k)
}
}
return nil
}
}
func testAccCheckCloudStackPortForwardDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_port_forward" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No port forward ID is set")
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuid") {
continue
}
p := cs.Firewall.NewDeletePortForwardingRuleParams(uuid)
_, err := cs.Firewall.DeletePortForwardingRule(p)
if err != nil {
return err
}
}
}
return nil
}
var testAccCloudStackPortForward_basic = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}
resource "cloudstack_port_forward" "foo" {
ipaddress = "%s"
forward {
protocol = "tcp"
private_port = 443
public_port = 8443
virtual_machine = "${cloudstack_instance.foobar.name}"
}
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_PUBLIC_IPADDRESS)
var testAccCloudStackPortForward_update = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform"
service_offering= "%s"
network = "%s"
template = "%s"
zone = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}
resource "cloudstack_port_forward" "foo" {
ipaddress = "%s"
forward {
protocol = "tcp"
private_port = 443
public_port = 8443
virtual_machine = "${cloudstack_instance.foobar.name}"
}
forward {
protocol = "tcp"
private_port = 80
public_port = 8080
virtual_machine = "${cloudstack_instance.foobar.name}"
}
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE,
CLOUDSTACK_PUBLIC_IPADDRESS)

View File

@ -0,0 +1,168 @@
package cloudstack
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func resourceCloudStackVPC() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackVPCCreate,
Read: resourceCloudStackVPCRead,
Update: resourceCloudStackVPCUpdate,
Delete: resourceCloudStackVPCDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"display_text": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"vpc_offering": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceCloudStackVPCCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
name := d.Get("name").(string)
// Retrieve the vpc_offering UUID
vpcofferingid, e := retrieveUUID(cs, "vpc_offering", d.Get("vpc_offering").(string))
if e != nil {
return e.Error()
}
// Retrieve the zone UUID
zoneid, e := retrieveUUID(cs, "zone", d.Get("zone").(string))
if e != nil {
return e.Error()
}
// Set the display text
displaytext, ok := d.GetOk("display_text")
if !ok {
displaytext = d.Get("name")
}
// Create a new parameter struct
p := cs.VPC.NewCreateVPCParams(d.Get("cidr").(string), displaytext.(string), name, vpcofferingid, zoneid)
// Create the new VPC
r, err := cs.VPC.CreateVPC(p)
if err != nil {
return fmt.Errorf("Error creating VPC %s: %s", name, err)
}
d.SetId(r.Id)
return resourceCloudStackVPCRead(d, meta)
}
func resourceCloudStackVPCRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the VPC details
v, count, err := cs.VPC.GetVPCByID(d.Id())
if err != nil {
if count == 0 {
log.Printf(
"[DEBUG] VPC %s does no longer exist", d.Get("name").(string))
d.SetId("")
return nil
}
return err
}
d.Set("name", v.Name)
d.Set("display_test", v.Displaytext)
d.Set("cidr", v.Cidr)
d.Set("zone", v.Zonename)
// Get the VPC offering details
o, _, err := cs.VPC.GetVPCOfferingByID(v.Vpcofferingid)
if err != nil {
return err
}
d.Set("vpc_offering", o.Name)
return nil
}
func resourceCloudStackVPCUpdate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Check if the name or display text is changed
if d.HasChange("name") || d.HasChange("display_text") {
// Create a new parameter struct
p := cs.VPC.NewUpdateVPCParams(d.Id(), d.Get("name").(string))
// Set the display text
displaytext, ok := d.GetOk("display_text")
if !ok {
displaytext = d.Get("name")
}
// Set the (new) display text
p.SetDisplaytext(displaytext.(string))
// Update the VPC
_, err := cs.VPC.UpdateVPC(p)
if err != nil {
return fmt.Errorf(
"Error updating VPC %s: %s", d.Get("name").(string), err)
}
}
return resourceCloudStackVPCRead(d, meta)
}
func resourceCloudStackVPCDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create a new parameter struct
p := cs.VPC.NewDeleteVPCParams(d.Id())
// Delete the VPC
_, err := cs.VPC.DeleteVPC(p)
if err != nil {
// This is a very poor way to be told the UUID 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 VPC %s: %s", d.Get("name").(string), err)
}
return nil
}

View File

@ -0,0 +1,118 @@
package cloudstack
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackVPC_basic(t *testing.T) {
var vpc cloudstack.VPC
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackVPCDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackVPC_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackVPCExists(
"cloudstack_vpc.foo", &vpc),
testAccCheckCloudStackVPCAttributes(&vpc),
resource.TestCheckResourceAttr(
"cloudstack_vpc.foo", "vpc_offering", CLOUDSTACK_VPC_OFFERING),
),
},
},
})
}
func testAccCheckCloudStackVPCExists(
n string, vpc *cloudstack.VPC) 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 VPC ID is set")
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
v, _, err := cs.VPC.GetVPCByID(rs.Primary.ID)
if err != nil {
return err
}
if v.Id != rs.Primary.ID {
return fmt.Errorf("VPC not found")
}
*vpc = *v
return nil
}
}
func testAccCheckCloudStackVPCAttributes(
vpc *cloudstack.VPC) resource.TestCheckFunc {
return func(s *terraform.State) error {
if vpc.Name != "terraform-vpc" {
return fmt.Errorf("Bad name: %s", vpc.Name)
}
if vpc.Displaytext != "terraform-vpc-text" {
return fmt.Errorf("Bad display text: %s", vpc.Displaytext)
}
if vpc.Cidr != CLOUDSTACK_VPC_CIDR {
return fmt.Errorf("Bad VPC offering: %s", vpc.Cidr)
}
return nil
}
}
func testAccCheckCloudStackVPCDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_vpc" {
continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No VPC ID is set")
}
p := cs.VPC.NewDeleteVPCParams(rs.Primary.ID)
err, _ := cs.VPC.DeleteVPC(p)
if err != nil {
return fmt.Errorf(
"Error deleting VPC (%s): %s",
rs.Primary.ID, err)
}
}
return nil
}
var testAccCloudStackVPC_basic = fmt.Sprintf(`
resource "cloudstack_vpc" "foo" {
name = "terraform-vpc"
display_text = "terraform-vpc-text"
cidr = "%s"
vpc_offering = "%s"
zone = "%s"
}`,
CLOUDSTACK_VPC_CIDR,
CLOUDSTACK_VPC_OFFERING,
CLOUDSTACK_ZONE)

View File

@ -0,0 +1,77 @@
package cloudstack
import (
"fmt"
"log"
"regexp"
"github.com/xanzy/go-cloudstack/cloudstack"
)
type retrieveError struct {
name string
value string
err error
}
func (e *retrieveError) Error() error {
return fmt.Errorf("Error retrieving UUID of %s %s: %s", e.name, e.value, e.err)
}
func retrieveUUID(cs *cloudstack.CloudStackClient, name, value string) (uuid string, e *retrieveError) {
// If the supplied value isn't a UUID, try to retrieve the UUID ourselves
if isUUID(value) {
return value, nil
}
log.Printf("[DEBUG] Retrieving UUID of %s: %s", name, value)
var err error
switch name {
case "disk_offering":
uuid, err = cs.DiskOffering.GetDiskOfferingID(value)
case "virtual_machine":
uuid, err = cs.VirtualMachine.GetVirtualMachineID(value)
case "service_offering":
uuid, err = cs.ServiceOffering.GetServiceOfferingID(value)
case "network_offering":
uuid, err = cs.NetworkOffering.GetNetworkOfferingID(value)
case "vpc_offering":
uuid, err = cs.VPC.GetVPCOfferingID(value)
case "vpc":
uuid, err = cs.VPC.GetVPCID(value)
case "template":
uuid, err = cs.Template.GetTemplateID(value, "all")
case "network":
uuid, err = cs.Network.GetNetworkID(value)
case "zone":
uuid, err = cs.Zone.GetZoneID(value)
case "ipaddress":
p := cs.Address.NewListPublicIpAddressesParams()
p.SetIpaddress(value)
l, e := cs.Address.ListPublicIpAddresses(p)
if e != nil {
err = e
break
}
if l.Count == 1 {
uuid = l.PublicIpAddresses[0].Id
break
}
err = fmt.Errorf("Could not find UUID of IP address: %s", value)
default:
return uuid, &retrieveError{name: name, value: value,
err: fmt.Errorf("Unknown request: %s", name)}
}
if err != nil {
return uuid, &retrieveError{name: name, value: value, err: err}
}
return uuid, nil
}
func isUUID(s string) bool {
re := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
return re.MatchString(s)
}

View File

@ -0,0 +1,45 @@
---
layout: "cloudstack"
page_title: "Provider: CloudStack"
sidebar_current: "docs-cloudstack-index"
description: |-
The CloudStack provider is used to interact with the many resources supported by CloudStack. The provider needs to be configured with a URL pointing to a runnning CloudStack API and the proper credentials before it can be used.
---
# CloudStack Provider
The CloudStack provider is used to interact with the many resources
supported by CloudStack. The provider needs to be configured with a
URL pointing to a runnning CloudStack API and the proper credentials
before it can be used.
Use the navigation to the left to read about the available resources.
## Example Usage
```
# Configure the CloudStack Provider
provider "cloudstack" {
api_url = "${var.cloudstack_api_url}"
api_key = "${var.cloudstack_api_key}"
secret_key = "${var.cloudstack_secret_key}"
}
# Create a web server
resource "cloudstack_instance" "web" {
...
}
```
## Argument Reference
The following arguments are supported:
* `api_url` - (Required) This is the CloudStack API URL. It must be provided, but
it can also be sourced from the `CLOUDSTACK_API_URL` environment variable.
* `api_key` - (Required) This is the CloudStack API key. It must be provided, but
it can also be sourced from the `CLOUDSTACK_API_KEY` environment variable.
* `secret_key` - (Required) This is the CloudStack secret key. It must be provided,
but it can also be sourced from the `CLOUDSTACK_SECRET_KEY` environment variable.

View File

@ -0,0 +1,58 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_disk"
sidebar_current: "docs-cloudstack-resource-disk"
description: |-
Creates a disk volume from a disk offering. This disk volume will be attached to a virtual machine if the optional parameters are configured.
---
# cloudstack\_disk
Creates a disk volume from a disk offering. This disk volume will be attached to
a virtual machine if the optional parameters are configured.
## Example Usage
```
resource "cloudstack_disk" "default" {
name = "test-disk"
attach = "true"
disk_offering = "custom"
size = 50
virtual-machine = "server-1"
zone = "zone-1"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the disk volume. Changing this forces a new
resource to be created.
* `attach` - (Optional) Determines whether or not to attach the disk volume to a
virtual machine (defaults false).
* `device` - (Optional) The device to map the disk volume to within the guest OS.
* `disk_offering` - (Required) The name of the disk offering to use for this
disk volume.
* `size` - (Optional) The size of the disk volume in gigabytes.
* `shrink_ok` - (Optional) Verifies if the disk volume is allowed to shrink when
resizing (defaults false).
* `virtual_machine` - (Optional) The name of the virtual machine to which you
want to attach the disk volume.
* `zone` - (Required) The name of the zone where this disk volume will be available.
Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the disk volume.
* `device` - The device the disk volume is mapped to within the guest OS.

View File

@ -0,0 +1,57 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_firewall"
sidebar_current: "docs-cloudstack-resource-firewall"
description: |-
Creates firewall rules for a given ip address.
---
# cloudstack\_firewall
Creates firewall rules for a given ip address.
## Example Usage
```
resource "cloudstack_firewall" "default" {
ipaddress = "192.168.0.1"
rule {
source_cidr = "10.0.0.0/8"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
}
```
## Argument Reference
The following arguments are supported:
* `ipaddress` - (Required) The ip address for which to create the firewall rules.
Changing this forces a new resource to be created.
* `rule` - (Required) Can be specified multiple times. Each rule block supports
fields documented below.
The `rule` block supports:
* `source_cidr` - (Required) The source cidr to allow access to the given ports.
* `protocol` - (Required) The name of the protocol to allow. Valid options are:
`tcp`, `udp` and `icmp`.
* `icmp_type` - (Optional) The ICMP type to allow. This can only be specified if
the protocol is ICMP.
* `icmp_code` - (Optional) The ICMP code to allow. This can only be specified if
the protocol is ICMP.
* `ports` - (Optional) List of ports and/or port ranges to allow. This can only
be specified if the protocol is TCP or UDP.
## Attributes Reference
The following attributes are exported:
* `ipaddress` - The ip address for which the firewall rules are created.

View File

@ -0,0 +1,59 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_instance"
sidebar_current: "docs-cloudstack-resource-instance"
description: |-
Creates and automatically starts a virtual machine based on a service offering, disk offering, and template.
---
# cloudstack\_instance
Creates and automatically starts a virtual machine based on a service offering,
disk offering, and template.
## Example Usage
```
resource "cloudstack_instance" "web" {
ami = "ami-1234"
instance_type = "m1.small"
tags {
Name = "HelloWorld"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the instance. Changing this forces a new
resource to be created.
* `display_name` - (Optional) The display name of the instance.
* `service_offering` - (Required) The service offering used for this instance.
* `network` - (Optional) The name of the network to connect this instance to.
Changing this forces a new resource to be created.
* `ipaddress` - (Optional) The IP address to assign to this instance. Changing
this forces a new resource to be created.
* `template` - (Required) The name of the template used for this instance.
Changing this forces a new resource to be created.
* `zone` - (Required) The name of the zone where this instance will be created.
Changing this forces a new resource to be created.
* `user_data` - (Optional) The user data to provide when launching the instance.
* `expunge` - (Optional) This determines if the instance is expunged when it is
destroyed (defaults false)
## Attributes Reference
The following attributes are exported:
* `id` - The instance ID.
* `display_name` - The display name of the instance.

View File

@ -0,0 +1,38 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_ipaddress"
sidebar_current: "docs-cloudstack-resource-ipaddress"
description: |-
Acquires and associates a public IP.
---
# cloudstack\_ipaddress
Acquires and associates a public IP.
## Example Usage
```
resource "cloudstack_ipaddress" "default" {
network = "test-network"
}
```
## Argument Reference
The following arguments are supported:
* `network` - (Optional) The name of the network for which an IP address should
be aquired and accociated. Changing this forces a new resource to be created.
* `vpc` - (Optional) The name of the vpc for which an IP address should
be aquired and accociated. Changing this forces a new resource to be created.
*NOTE: Either `network` or `vpc` should have a value!*
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the aquired and accociated IP address.
* `ipaddress` - The IP address that was aquired and accociated.

View File

@ -0,0 +1,54 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_network"
sidebar_current: "docs-cloudstack-resource-network"
description: |-
Creates a network.
---
# cloudstack\_network
Creates a network.
## Example Usage
Basic usage:
```
resource "cloudstack_network" "default" {
name = "test-network"
cidr = "10.0.0.0/16"
network_offering = "Default Network"
zone = "zone-1"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the network.
* `display_text` - (Optional) The display text of the network.
* `cidr` - (Required) The CIDR block for the network. Changing this forces a new
resource to be created.
* `network_offering` - (Required) The name of the network offering to use for
this network.
* `vpc` - (Optional) The name of the vpc to create this network for. Changing
this forces a new resource to be created.
* `aclid` - (Optional) The ID of a network ACL that should be attached to the
network. Changing this forces a new resource to be created.
* `zone` - (Required) The name of the zone where this disk volume will be
available. Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the network.
* `display_text` - The display text of the network.

View File

@ -0,0 +1,62 @@
---
layout: "aws"
page_title: "AWS: aws_network_acl"
sidebar_current: "docs-aws-resource-network-acl"
description: |-
Provides an network ACL resource.
---
# aws\_network\_acl
Provides an network ACL resource. You might set up network ACLs with rules similar
to your security groups in order to add an additional layer of security to your VPC.
## Example Usage
```
resource "aws_network_acl" "main" {
vpc_id = "${aws_vpc.main.id}"
egress = {
protocol = "tcp"
rule_no = 2
action = "allow"
cidr_block = "10.3.2.3/18"
from_port = 443
to_port = 443
}
ingress = {
protocol = "tcp"
rule_no = 1
action = "allow"
cidr_block = "10.3.10.3/18"
from_port = 80
to_port = 80
}
}
```
## Argument Reference
The following arguments are supported:
* `vpc_id` - (Required) The ID of the associated VPC.
* `subnet_id` - (Optional) The ID of the associated subnet.
* `ingress` - (Optional) Specifies an ingress rule. Parameters defined below.
* `egress` - (Optional) Speicifes an egress rule. Parameters defined below.
Both `egress` and `ingress` support the following keys:
* `from_port` - (Required) The from port to match.
* `to_port` - (Required) The to port to match.
* `rule_no` - (Required) The rule number. Used for ordering.
* `action` - (Required) The action to take.
* `protocol` - (Required) The protocol to match.
* `cidr_block` - (Optional) The CIDR block to match.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the network ACL

View File

@ -0,0 +1,65 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_network_acl_rule"
sidebar_current: "docs-cloudstack-resource-network_acl_rule"
description: |-
Creates network ACL rules for a given network ACL.
---
# cloudstack\_network\_acl\_rule
Creates network ACL rules for a given network ACL.
## Example Usage
```
resource "cloudstack_network_acl_rule" "default" {
aclid = "f3843ce0-334c-4586-bbd3-0c2e2bc946c6"
rule {
action = "allow"
source_cidr = "10.0.0.0/8"
protocol = "tcp"
ports = ["80", "1000-2000"]
traffic_type = "ingress"
}
}
```
## Argument Reference
The following arguments are supported:
* `aclid` - (Required) The network ACL ID for which to create the rules.
Changing this forces a new resource to be created.
* `rule` - (Required) Can be specified multiple times. Each rule block supports
fields documented below.
The `rule` block supports:
* `action` - (Optional) The action for the rule. Valid options are: `allow` and
`deny` (defaults allow).
* `source_cidr` - (Required) The source cidr to allow access to the given ports.
* `protocol` - (Required) The name of the protocol to allow. Valid options are:
`tcp`, `udp`, `icmp`, `all` or a valid protocol number.
* `icmp_type` - (Optional) The ICMP type to allow. This can only be specified if
the protocol is ICMP.
* `icmp_code` - (Optional) The ICMP code to allow. This can only be specified if
the protocol is ICMP.
* `ports` - (Optional) List of ports and/or port ranges to allow. This can only
be specified if the protocol is TCP, UDP, ALL or a valid protocol number.
* `traffic_type` - (Optional) The traffic type for the rule. Valid options are:
`ingress` or `egress` (defaults ingress).
## Attributes Reference
The following attributes are exported:
* `aclid` - The ACL ID for which the rules are created.

View File

@ -0,0 +1,43 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_nic"
sidebar_current: "docs-cloudstack-resource-nic"
description: |-
Creates an additional NIC to add a VM to the specified network.
---
# cloudstack\_nic
Creates an additional NIC to add a VM to the specified network.
## Example Usage
Basic usage:
```
resource "cloudstack_nic" "test" {
network = "network-2"
ipaddress = "192.168.1.1"
virtual_machine = "server-1"
}
```
## Argument Reference
The following arguments are supported:
* `network` - (Required) The name of the network to plug the NIC into. Changing
this forces a new resource to be created.
* `ipaddress` - (Optional) The IP address to assign to the NIC. Changing this
forces a new resource to be created.
* `virtual_machine` - (Required) The name of the virtual machine to which to
attach the NIC. Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the NIC.
* `ipaddress` - The assigned IP address.

View File

@ -0,0 +1,53 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_port_forward"
sidebar_current: "docs-cloudstack-resource-port-forward"
description: |-
Creates port forwards.
---
# cloudstack\_port\_forward
Creates port forwards.
## Example Usage
```
resource "cloudstack_port_forward" "default" {
ipaddress = "192.168.0.1"
forward {
protocol = "tcp"
private_port = 80
public_port = 8080
virtual_machine = "server-1"
}
}
```
## Argument Reference
The following arguments are supported:
* `ipaddress` - (Required) The ip address for which to create the port forwards.
Changing this forces a new resource to be created.
* `forward` - (Required) Can be specified multiple times. Each forward block supports
fields documented below.
The `forward` block supports:
* `protocol` - (Required) The name of the protocol to allow. Valid options are:
`tcp` and `udp`.
* `private_port` - (Required) The private port to forward to.
* `public_port` - (Required) The public port to forward from.
* `virtual_machine` - (Required) The name of the virtual machine to forward to.
## Attributes Reference
The following attributes are exported:
* `ipaddress` - The ip address for which the port forwards are created.

View File

@ -0,0 +1,48 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_vpc"
sidebar_current: "docs-cloudstack-resource-vpc"
description: |-
Creates a VPC.
---
# cloudstack\_vpc
Creates a VPC.
## Example Usage
Basic usage:
```
resource "cloudstack_vpc" "default" {
name = "test-vpc"
cidr = "10.0.0.0/16"
vpc_offering = "Default VPC Offering"
zone = "zone-1"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the VPC.
* `display_text` - (Optional) The display text of the VPC.
* `cidr` - (Required) The CIDR block for the VPC. Changing this forces a new
resource to be created.
* `vpc_offering` - (Required) The name of the VPC offering to use for this VPC.
Changing this forces a new resource to be created.
* `zone` - (Required) The name of the zone where this disk volume will be
available. Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the VPC.
* `display_text` - The display text of the VPC.

View File

@ -0,0 +1,62 @@
<% 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">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-cloudstack-index") %>>
<a href="/docs/providers/cloudstack/index.html">CloudStack Provider</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource") %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-cloudstack-resource-disk") %>>
<a href="/docs/providers/cloudstack/r/disk.html">cloudstack_disk</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-firewall") %>>
<a href="/docs/providers/cloudstack/r/firewall.html">cloudstack_firewall</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-instance") %>>
<a href="/docs/providers/cloudstack/r/instance.html">cloudstack_instance</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-ipaddress") %>>
<a href="/docs/providers/cloudstack/r/ipaddress.html">cloudstack_ipaddress</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-network") %>>
<a href="/docs/providers/cloudstack/r/network.html">cloudstack_network</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-network_acl") %>>
<a href="/docs/providers/cloudstack/r/network_acl.html">cloudstack_network_acl</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-network_acl_rule") %>>
<a href="/docs/providers/cloudstack/r/network_acl_rule.html">cloudstack_network_acl_rule</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-nic") %>>
<a href="/docs/providers/cloudstack/r/nic.html">cloudstack_nic</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-port-forward") %>>
<a href="/docs/providers/cloudstack/r/port_forward.html">cloudstack_port_forward</a>
</li>
<li<%= sidebar_current("docs-cloudstack-resource-vpc") %>>
<a href="/docs/providers/cloudstack/r/vpc.html">cloudstack_vpc</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>