Adding the ability to manage the whole firewall

This goes for the normal firewall, the egress firewall and the network
ACL.

USE WITH CAUTION! When setting `managed = true` in your config, it
means it will delete all firewall rules that are not in your config, so
unknown to TF.

Also adding the new `cloudstack_egress_firewall` resource with this
commit and updating go-cloudstack to the latest API version (v4.4)
This commit is contained in:
Sander van Harmelen 2015-01-15 10:04:09 +01:00
parent d01670f49b
commit 3898098c78
9 changed files with 781 additions and 147 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,484 @@
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: resourceCloudStackFirewallRuleHash,
},
},
}
}
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: resourceCloudStackFirewallRuleHash,
}
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: resourceCloudStackFirewallRuleHash,
}
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 := 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, 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 := 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 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 {
// 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,189 @@
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.1702320581.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.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", "ipaddress", CLOUDSTACK_NETWORK_1),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.#", "1"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.1889509032", "80"),
),
},
resource.TestStep{
Config: testAccCloudStackEgressFirewall_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "ipaddress", CLOUDSTACK_NETWORK_1),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.source_cidr", "10.0.0.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.1209010669", "1000-2000"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.1702320581.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.3779782959.source_cidr", "172.16.100.0/24"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.3779782959.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.3779782959.ports.#", "2"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.3779782959.ports.1889509032", "80"),
resource.TestCheckResourceAttr(
"cloudstack_egress_firewall.foo", "rule.3779782959.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") {
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") {
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" {
ipaddress = "%s"
rule {
source_cidr = "10.0.0.0/24"
protocol = "tcp"
ports = ["80", "1000-2000"]
}
}`, CLOUDSTACK_NETWORK_1)
var testAccCloudStackEgressFirewall_update = fmt.Sprintf(`
resource "cloudstack_egress_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_NETWORK_1)

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)
@ -415,7 +453,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

@ -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

@ -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 {
@ -438,7 +484,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 +515,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

@ -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")