Merge pull request #809 from svanharmelen/f-provider/cloudstack-extend-features

provider/cloudstack: extend features and adding a new resource
This commit is contained in:
Sander van Harmelen 2015-01-15 21:51:51 +01:00
commit e4867b6fbc
12 changed files with 818 additions and 158 deletions

View File

@ -38,6 +38,7 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"cloudstack_disk": resourceCloudStackDisk(),
"cloudstack_egress_firewall": resourceCloudStackEgressFirewall(),
"cloudstack_firewall": resourceCloudStackFirewall(),
"cloudstack_instance": resourceCloudStackInstance(),
"cloudstack_ipaddress": resourceCloudStackIPAddress(),

View File

@ -38,131 +38,9 @@ func testAccPreCheck(t *testing.T) {
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!!
// SET THESE VALUES IN ORDER TO RUN THE ACC TESTS!!
var CLOUDSTACK_DISK_OFFERING_1 = ""
var CLOUDSTACK_DISK_OFFERING_2 = ""
var CLOUDSTACK_SERVICE_OFFERING_1 = ""

View File

@ -183,10 +183,7 @@ func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) erro
}
// Create a new parameter struct
p := cs.Volume.NewResizeVolumeParams()
// Set the volume UUID
p.SetId(d.Id())
p := cs.Volume.NewResizeVolumeParams(d.Id())
// Retrieve the disk_offering UUID
diskofferingid, e := retrieveUUID(cs, "disk_offering", d.Get("disk_offering").(string))

View File

@ -0,0 +1,489 @@
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 resourceCloudStackEgressFirewall() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackEgressFirewallCreate,
Read: resourceCloudStackEgressFirewallRead,
Update: resourceCloudStackEgressFirewallUpdate,
Delete: resourceCloudStackEgressFirewallDelete,
Schema: map[string]*schema.Schema{
"network": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"managed": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"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: resourceCloudStackEgressFirewallRuleHash,
},
},
}
}
func resourceCloudStackEgressFirewallCreate(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()
}
// We need to set this upfront in order to be able to save a partial state
d.SetId(networkid)
// 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: resourceCloudStackEgressFirewallRuleHash,
}
for _, rule := range rs.List() {
// Create a single rule
err := resourceCloudStackEgressFirewallCreateRule(d, meta, 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 resourceCloudStackEgressFirewallRead(d, meta)
}
func resourceCloudStackEgressFirewallCreateRule(
d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
uuids := rule["uuids"].(map[string]interface{})
// Make sure all required rule parameters are there
if err := verifyEgressFirewallRuleParams(d, rule); err != nil {
return err
}
// Create a new parameter struct
p := cs.Firewall.NewCreateEgressFirewallRuleParams(d.Id(), 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.CreateEgressFirewallRule(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.CreateEgressFirewallRule(p)
if err != nil {
return err
}
ports.Add(port)
rule["ports"] = ports
uuids[port.(string)] = r.Id
rule["uuids"] = uuids
}
}
}
return nil
}
func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceCloudStackEgressFirewallRuleHash,
}
if d.Get("managed").(bool) {
// Read all rules...
}
// 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.GetEgressFirewallRuleByID(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.GetEgressFirewallRuleByID(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 this is a managed firewall, add all unknown rules into a single dummy rule
if d.Get("managed").(bool) {
// Get all the rules from the running environment
p := cs.Firewall.NewListEgressFirewallRulesParams()
p.SetNetworkid(d.Id())
p.SetListall(true)
r, err := cs.Firewall.ListEgressFirewallRules(p)
if err != nil {
return err
}
// Add all UUIDs to the uuids map
uuids := make(map[string]interface{})
for _, r := range r.EgressFirewallRules {
uuids[r.Id] = r.Id
}
// Delete all expected UUIDs from the uuids map
for _, rule := range rules.List() {
rule := rule.(map[string]interface{})
for _, id := range rule["uuids"].(map[string]interface{}) {
delete(uuids, id.(string))
}
}
if len(uuids) > 0 {
// Make a dummy rule to hold all unknown UUIDs
rule := map[string]interface{}{
"source_cidr": "N/A",
"protocol": "N/A",
"uuids": uuids,
}
// Add the dummy rule to the rules set
rules.Add(rule)
}
}
if rules.Len() > 0 {
d.Set("rule", rules)
} else {
d.SetId("")
}
return nil
}
func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) 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 := resourceCloudStackEgressFirewallDeleteRule(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 := resourceCloudStackEgressFirewallCreateRule(
d, meta, 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 resourceCloudStackEgressFirewallRead(d, meta)
}
func resourceCloudStackEgressFirewallDelete(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 := resourceCloudStackEgressFirewallDeleteRule(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 resourceCloudStackEgressFirewallDeleteRule(
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 {
// We don't care about the count here, so just continue
if k == "#" {
continue
}
// Create the parameter struct
p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string))
// Delete the rule
if _, err := cs.Firewall.DeleteEgressFirewallRule(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 resourceCloudStackEgressFirewallRuleHash(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 verifyEgressFirewallRuleParams(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,200 @@
package cloudstack
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/xanzy/go-cloudstack/cloudstack"
)
func TestAccCloudStackEgressFirewall_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackEgressFirewallDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackEgressFirewall_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "network", CLOUDSTACK_NETWORK_1),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo",
"rule.411689741.source_cidr",
CLOUDSTACK_NETWORK_1_IPADDRESS+"/32"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.1889509032", "80"),
),
},
},
})
}
func TestAccCloudStackEgressFirewall_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackEgressFirewallDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCloudStackEgressFirewall_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "network", CLOUDSTACK_NETWORK_1),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo",
"rule.411689741.source_cidr",
CLOUDSTACK_NETWORK_1_IPADDRESS+"/32"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.1889509032", "80"),
),
},
resource.TestStep{
Config: testAccCloudStackEgressFirewall_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "network", CLOUDSTACK_NETWORK_1),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo",
"rule.411689741.source_cidr",
CLOUDSTACK_NETWORK_1_IPADDRESS+"/32"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.411689741.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo",
"rule.845479598.source_cidr",
CLOUDSTACK_NETWORK_1_IPADDRESS+"/32"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.845479598.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.845479598.ports.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.845479598.ports.3638101695", "443"),
),
},
},
})
}
func testAccCheckCloudStackEgressFirewallRulesExist(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.") || strings.HasSuffix(k, ".uuids.#") {
continue
}
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
_, count, err := cs.Firewall.GetEgressFirewallRuleByID(uuid)
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("Firewall rule for %s not found", k)
}
}
return nil
}
}
func testAccCheckCloudStackEgressFirewallDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudstack_egress_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.") || strings.HasSuffix(k, ".uuids.#") {
continue
}
p := cs.Firewall.NewDeleteEgressFirewallRuleParams(uuid)
_, err := cs.Firewall.DeleteEgressFirewallRule(p)
if err != nil {
return err
}
}
}
return nil
}
var testAccCloudStackEgressFirewall_basic = fmt.Sprintf(`
resource "cloudstack_egress_firewall" "foo" {
network = "%s"
rule {
source_cidr = "%s/32"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
}`,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_NETWORK_1_IPADDRESS)
var testAccCloudStackEgressFirewall_update = fmt.Sprintf(`
resource "cloudstack_egress_firewall" "foo" {
network = "%s"
rule {
source_cidr = "%s/32"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
rule {
source_cidr = "%s/32"
protocol = "tcp"
ports = ["443"]
}
}`,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_NETWORK_1_IPADDRESS,
CLOUDSTACK_NETWORK_1_IPADDRESS)

View File

@ -27,6 +27,12 @@ func resourceCloudStackFirewall() *schema.Resource {
ForceNew: true,
},
"managed": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"rule": &schema.Schema{
Type: schema.TypeSet,
Required: true,
@ -85,7 +91,7 @@ func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{})
}
// We need to set this upfront in order to be able to save a partial state
d.SetId(d.Get("ipaddress").(string))
d.SetId(ipaddressid)
// Create all rules that are configured
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
@ -97,7 +103,7 @@ func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{})
for _, rule := range rs.List() {
// Create a single rule
err := resourceCloudStackFirewallCreateRule(d, meta, ipaddressid, rule.(map[string]interface{}))
err := resourceCloudStackFirewallCreateRule(d, meta, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
@ -113,17 +119,17 @@ func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{})
}
func resourceCloudStackFirewallCreateRule(
d *schema.ResourceData, meta interface{}, ipaddressid string, rule map[string]interface{}) error {
d *schema.ResourceData, meta interface{}, 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 {
// Make sure all required rule parameters are there
if err := verifyFirewallRuleParams(d, rule); err != nil {
return err
}
// Create a new parameter struct
p := cs.Firewall.NewCreateFirewallRuleParams(ipaddressid, rule["protocol"].(string))
p := cs.Firewall.NewCreateFirewallRuleParams(d.Id(), rule["protocol"].(string))
// Set the CIDR list
p.SetCidrlist([]string{rule["source_cidr"].(string)})
@ -274,6 +280,46 @@ func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) er
}
}
// If this is a managed firewall, add all unknown rules into a single dummy rule
if d.Get("managed").(bool) {
// Get all the rules from the running environment
p := cs.Firewall.NewListFirewallRulesParams()
p.SetIpaddressid(d.Id())
p.SetListall(true)
r, err := cs.Firewall.ListFirewallRules(p)
if err != nil {
return err
}
// Add all UUIDs to the uuids map
uuids := make(map[string]interface{})
for _, r := range r.FirewallRules {
uuids[r.Id] = r.Id
}
// Delete all expected UUIDs from the uuids map
for _, rule := range rules.List() {
rule := rule.(map[string]interface{})
for _, id := range rule["uuids"].(map[string]interface{}) {
delete(uuids, id.(string))
}
}
if len(uuids) > 0 {
// Make a dummy rule to hold all unknown UUIDs
rule := map[string]interface{}{
"source_cidr": "N/A",
"protocol": "N/A",
"uuids": uuids,
}
// Add the dummy rule to the rules set
rules.Add(rule)
}
}
if rules.Len() > 0 {
d.Set("rule", rules)
} else {
@ -284,14 +330,6 @@ func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) er
}
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")
@ -315,7 +353,7 @@ func resourceCloudStackFirewallUpdate(d *schema.ResourceData, meta interface{})
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{}))
d, meta, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
@ -355,6 +393,11 @@ func resourceCloudStackFirewallDeleteRule(
uuids := rule["uuids"].(map[string]interface{})
for k, id := range uuids {
// We don't care about the count here, so just continue
if k == "#" {
continue
}
// Create the parameter struct
p := cs.Firewall.NewDeleteFirewallRuleParams(id.(string))
@ -415,7 +458,7 @@ func resourceCloudStackFirewallRuleHash(v interface{}) int {
return hashcode.String(buf.String())
}
func verifyFirewallParams(d *schema.ResourceData, rule map[string]interface{}) error {
func verifyFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
protocol := rule["protocol"].(string)
if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
return fmt.Errorf(

View File

@ -111,7 +111,7 @@ func testAccCheckCloudStackFirewallRulesExist(n string) resource.TestCheckFunc {
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
if !strings.Contains(k, ".uuids.") || strings.HasSuffix(k, ".uuids.#") {
continue
}
@ -144,7 +144,7 @@ func testAccCheckCloudStackFirewallDestroy(s *terraform.State) error {
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
if !strings.Contains(k, ".uuids.") || strings.HasSuffix(k, ".uuids.#") {
continue
}

View File

@ -147,7 +147,8 @@ func verifyIPAddressParams(d *schema.ResourceData) error {
_, 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 fmt.Errorf(
"You must supply a value for either (so not both) the 'network' or 'vpc' parameter")
}
return nil

View File

@ -79,13 +79,13 @@ func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) e
}
// Compute/set the display text
displaytext := d.Get("display_text").(string)
if displaytext == "" {
displaytext, ok := d.GetOk("display_text")
if !ok {
displaytext = name
}
// Create a new parameter struct
p := cs.Network.NewCreateNetworkParams(displaytext, name, networkofferingid, zoneid)
p := cs.Network.NewCreateNetworkParams(displaytext.(string), name, networkofferingid, zoneid)
// Get the network details from the CIDR
m, err := parseCIDR(d.Get("cidr").(string))
@ -147,7 +147,7 @@ func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) err
}
d.Set("name", n.Name)
d.Set("display_test", n.Displaytext)
d.Set("display_text", n.Displaytext)
d.Set("cidr", n.Cidr)
d.Set("network_offering", n.Networkofferingname)
d.Set("zone", n.Zonename)

View File

@ -27,6 +27,12 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
ForceNew: true,
},
"managed": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"rule": &schema.Schema{
Type: schema.TypeSet,
Required: true,
@ -295,6 +301,46 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
}
}
// If this is a managed firewall, add all unknown rules into a single dummy rule
if d.Get("managed").(bool) {
// Get all the rules from the running environment
p := cs.NetworkACL.NewListNetworkACLsParams()
p.SetAclid(d.Id())
p.SetListall(true)
r, err := cs.NetworkACL.ListNetworkACLs(p)
if err != nil {
return err
}
// Add all UUIDs to the uuids map
uuids := make(map[string]interface{})
for _, r := range r.NetworkACLs {
uuids[r.Id] = r.Id
}
// Delete all expected UUIDs from the uuids map
for _, rule := range rules.List() {
rule := rule.(map[string]interface{})
for _, id := range rule["uuids"].(map[string]interface{}) {
delete(uuids, id.(string))
}
}
if len(uuids) > 0 {
// Make a dummy rule to hold all unknown UUIDs
rule := map[string]interface{}{
"source_cidr": "N/A",
"protocol": "N/A",
"uuids": uuids,
}
// Add the dummy rule to the rules set
rules.Add(rule)
}
}
if rules.Len() > 0 {
d.Set("rule", rules)
} else {
@ -371,6 +417,11 @@ func resourceCloudStackNetworkACLRuleDeleteRule(
uuids := rule["uuids"].(map[string]interface{})
for k, id := range uuids {
// We don't care about the count here, so just continue
if k == "#" {
continue
}
// Create the parameter struct
p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string))
@ -438,7 +489,7 @@ func resourceCloudStackNetworkACLRuleHash(v interface{}) int {
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")
return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values")
}
protocol := rule["protocol"].(string)
@ -469,7 +520,7 @@ func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interfac
traffic := rule["traffic_type"].(string)
if traffic != "ingress" && traffic != "egress" {
return fmt.Errorf(
"Parameter traffic_type only excepts 'ingress' or 'egress' as values")
"Parameter traffic_type only accepts 'ingress' or 'egress' as values")
}
return nil

View File

@ -104,7 +104,7 @@ func TestAccCloudStackNetworkACLRule_update(t *testing.T) {
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.4267872693.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_network_acl_rule.foo", "rule.4267872693.traffic_type", "engress"),
"cloudstack_network_acl_rule.foo", "rule.4267872693.traffic_type", "egress"),
),
},
},
@ -123,7 +123,7 @@ func testAccCheckCloudStackNetworkACLRulesExist(n string) resource.TestCheckFunc
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
if !strings.Contains(k, ".uuids.") || strings.HasSuffix(k, ".uuids.#") {
continue
}
@ -156,7 +156,7 @@ func testAccCheckCloudStackNetworkACLRuleDestroy(s *terraform.State) error {
}
for k, uuid := range rs.Primary.Attributes {
if !strings.Contains(k, "uuids") {
if !strings.Contains(k, ".uuids.") || strings.HasSuffix(k, ".uuids.#") {
continue
}

View File

@ -69,7 +69,7 @@ func resourceCloudStackVPCCreate(d *schema.ResourceData, meta interface{}) error
// Set the display text
displaytext, ok := d.GetOk("display_text")
if !ok {
displaytext = d.Get("name")
displaytext = name
}
// Create a new parameter struct
@ -103,7 +103,7 @@ func resourceCloudStackVPCRead(d *schema.ResourceData, meta interface{}) error {
}
d.Set("name", v.Name)
d.Set("display_test", v.Displaytext)
d.Set("display_text", v.Displaytext)
d.Set("cidr", v.Cidr)
d.Set("zone", v.Zonename)
@ -124,7 +124,7 @@ func resourceCloudStackVPCUpdate(d *schema.ResourceData, meta interface{}) error
// 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))
p := cs.VPC.NewUpdateVPCParams(d.Id())
// Set the display text
displaytext, ok := d.GetOk("display_text")