diff --git a/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go b/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go index 9f672c7a2a..6ef112980c 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go +++ b/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go @@ -35,7 +35,7 @@ func resourceCloudStackEgressFirewall() *schema.Resource { "rule": &schema.Schema{ Type: schema.TypeSet, - Required: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "source_cidr": &schema.Schema{ @@ -84,6 +84,11 @@ func resourceCloudStackEgressFirewall() *schema.Resource { func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + // Make sure all required parameters are there + if err := verifyEgressFirewallParams(d); err != nil { + return err + } + // Retrieve the network UUID networkid, e := retrieveUUID(cs, "network", d.Get("network").(string)) if e != nil { @@ -203,10 +208,6 @@ func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface 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() { @@ -285,7 +286,8 @@ func resourceCloudStackEgressFirewallRead(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) { + managed := d.Get("managed").(bool) + if managed { // Get all the rules from the running environment p := cs.Firewall.NewListEgressFirewallRulesParams() p.SetNetworkid(d.Id()) @@ -297,7 +299,7 @@ func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface } // Add all UUIDs to the uuids map - uuids := make(map[string]interface{}) + uuids := make(map[string]interface{}, len(r.EgressFirewallRules)) for _, r := range r.EgressFirewallRules { uuids[r.Id] = r.Id } @@ -326,7 +328,7 @@ func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface if rules.Len() > 0 { d.Set("rule", rules) - } else { + } else if !managed { d.SetId("") } @@ -334,6 +336,11 @@ func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface } func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error { + // Make sure all required parameters are there + if err := verifyEgressFirewallParams(d); err != nil { + return err + } + // Check if the rule set as a whole has changed if d.HasChange("rule") { o, n := d.GetChange("rule") @@ -462,6 +469,18 @@ func resourceCloudStackEgressFirewallRuleHash(v interface{}) int { return hashcode.String(buf.String()) } +func verifyEgressFirewallParams(d *schema.ResourceData) error { + managed := d.Get("managed").(bool) + _, rules := d.GetOk("rule") + + if !rules && !managed { + return fmt.Errorf( + "You must supply at least one 'rule' when not using the 'managed' firewall feature") + } + + return nil +} + func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { protocol := rule["protocol"].(string) if protocol != "tcp" && protocol != "udp" && protocol != "icmp" { diff --git a/builtin/providers/cloudstack/resource_cloudstack_firewall.go b/builtin/providers/cloudstack/resource_cloudstack_firewall.go index 75f0410e6e..ec12a54e3d 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_firewall.go +++ b/builtin/providers/cloudstack/resource_cloudstack_firewall.go @@ -35,7 +35,7 @@ func resourceCloudStackFirewall() *schema.Resource { "rule": &schema.Schema{ Type: schema.TypeSet, - Required: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "source_cidr": &schema.Schema{ @@ -84,6 +84,11 @@ func resourceCloudStackFirewall() *schema.Resource { func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + // Make sure all required parameters are there + if err := verifyFirewallParams(d); err != nil { + return err + } + // Retrieve the ipaddress UUID ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string)) if e != nil { @@ -281,7 +286,8 @@ 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) { + managed := d.Get("managed").(bool) + if managed { // Get all the rules from the running environment p := cs.Firewall.NewListFirewallRulesParams() p.SetIpaddressid(d.Id()) @@ -293,7 +299,7 @@ func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) er } // Add all UUIDs to the uuids map - uuids := make(map[string]interface{}) + uuids := make(map[string]interface{}, len(r.FirewallRules)) for _, r := range r.FirewallRules { uuids[r.Id] = r.Id } @@ -322,7 +328,7 @@ func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) er if rules.Len() > 0 { d.Set("rule", rules) - } else { + } else if !managed { d.SetId("") } @@ -330,6 +336,11 @@ func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) er } func resourceCloudStackFirewallUpdate(d *schema.ResourceData, meta interface{}) error { + // Make sure all required parameters are there + if err := verifyFirewallParams(d); err != nil { + return err + } + // Check if the rule set as a whole has changed if d.HasChange("rule") { o, n := d.GetChange("rule") @@ -458,6 +469,18 @@ func resourceCloudStackFirewallRuleHash(v interface{}) int { return hashcode.String(buf.String()) } +func verifyFirewallParams(d *schema.ResourceData) error { + managed := d.Get("managed").(bool) + _, rules := d.GetOk("rule") + + if !rules && !managed { + return fmt.Errorf( + "You must supply at least one 'rule' when not using the 'managed' firewall feature") + } + + return nil +} + func verifyFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { protocol := rule["protocol"].(string) if protocol != "tcp" && protocol != "udp" && protocol != "icmp" { diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go index 6e13e90c97..80484540a0 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go @@ -35,7 +35,7 @@ func resourceCloudStackNetworkACLRule() *schema.Resource { "rule": &schema.Schema{ Type: schema.TypeSet, - Required: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "action": &schema.Schema{ @@ -94,11 +94,13 @@ func resourceCloudStackNetworkACLRule() *schema.Resource { } func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error { - // Get the acl UUID - aclid := d.Get("aclid").(string) + // Make sure all required parameters are there + if err := verifyNetworkACLParams(d); err != nil { + return err + } // We need to set this upfront in order to be able to save a partial state - d.SetId(aclid) + d.SetId(d.Get("aclid").(string)) // Create all rules that are configured if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { @@ -110,8 +112,7 @@ func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interfa for _, rule := range rs.List() { // Create a single rule - err := resourceCloudStackNetworkACLRuleCreateRule( - d, meta, aclid, rule.(map[string]interface{})) + err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) // We need to update this first to preserve the correct state rules.Add(rule) @@ -127,7 +128,7 @@ func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interfa } func resourceCloudStackNetworkACLRuleCreateRule( - d *schema.ResourceData, meta interface{}, aclid 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{}) @@ -140,7 +141,7 @@ func resourceCloudStackNetworkACLRuleCreateRule( p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string)) // Set the acl ID - p.SetAclid(aclid) + p.SetAclid(d.Id()) // Set the action p.SetAction(rule["action"].(string)) @@ -302,7 +303,8 @@ 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) { + managed := d.Get("managed").(bool) + if managed { // Get all the rules from the running environment p := cs.NetworkACL.NewListNetworkACLsParams() p.SetAclid(d.Id()) @@ -314,7 +316,7 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface } // Add all UUIDs to the uuids map - uuids := make(map[string]interface{}) + uuids := make(map[string]interface{}, len(r.NetworkACLs)) for _, r := range r.NetworkACLs { uuids[r.Id] = r.Id } @@ -343,7 +345,7 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface if rules.Len() > 0 { d.Set("rule", rules) - } else { + } else if !managed { d.SetId("") } @@ -351,8 +353,10 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface } func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { - // Get the acl UUID - aclid := d.Get("aclid").(string) + // Make sure all required parameters are there + if err := verifyNetworkACLParams(d); err != nil { + return err + } // Check if the rule set as a whole has changed if d.HasChange("rule") { @@ -376,8 +380,7 @@ func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interfa // 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{})) + err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) // We need to update this first to preserve the correct state rules.Add(rule) @@ -486,6 +489,18 @@ func resourceCloudStackNetworkACLRuleHash(v interface{}) int { return hashcode.String(buf.String()) } +func verifyNetworkACLParams(d *schema.ResourceData) error { + managed := d.Get("managed").(bool) + _, rules := d.GetOk("rule") + + if !rules && !managed { + return fmt.Errorf( + "You must supply at least one 'rule' when not using the 'managed' firewall feature") + } + + return nil +} + func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { action := rule["action"].(string) if action != "allow" && action != "deny" { diff --git a/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown b/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown new file mode 100644 index 0000000000..17fa20927e --- /dev/null +++ b/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_egress_firewall" +sidebar_current: "docs-cloudstack-resource-egress-firewall" +description: |- + Creates egress firewall rules for a given network. +--- + +# cloudstack\_egress\_firewall + +Creates egress firewall rules for a given network. + +## Example Usage + +``` +resource "cloudstack_egress_firewall" "default" { + network = "test-network" + + rule { + source_cidr = "10.0.0.0/8" + protocol = "tcp" + ports = ["80", "1000-2000"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `network` - (Required) The network for which to create the egress firewall + rules. Changing this forces a new resource to be created. + +* `managed` - (Optional) USE WITH CAUTION! If enabled all the egress firewall + rules for this network will be managed by this resource. This means it will + delete all firewall rules that are not in your config! (defaults false) + +* `rule` - (Optional) Can be specified multiple times. Each rule block supports + fields documented below. If `managed = false` at least one rule is required! + +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: + +* `ID` - The network ID for which the egress firewall rules are created. diff --git a/website/source/docs/providers/cloudstack/r/firewall.html.markdown b/website/source/docs/providers/cloudstack/r/firewall.html.markdown index 3bae04d0e6..1c659e6bfc 100644 --- a/website/source/docs/providers/cloudstack/r/firewall.html.markdown +++ b/website/source/docs/providers/cloudstack/r/firewall.html.markdown @@ -31,8 +31,12 @@ 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. +* `managed` - (Optional) USE WITH CAUTION! If enabled all the firewall rules for + this IP address will be managed by this resource. This means it will delete + all firewall rules that are not in your config! (defaults false) + +* `rule` - (Optional) Can be specified multiple times. Each rule block supports + fields documented below. If `managed = false` at least one rule is required! The `rule` block supports: @@ -54,4 +58,4 @@ The `rule` block supports: The following attributes are exported: -* `ipaddress` - The IP address for which the firewall rules are created. +* `ID` - The IP address ID for which the firewall rules are created. diff --git a/website/source/docs/providers/cloudstack/r/network_acl.html.markdown b/website/source/docs/providers/cloudstack/r/network_acl.html.markdown index afead04ee6..487d0fe906 100644 --- a/website/source/docs/providers/cloudstack/r/network_acl.html.markdown +++ b/website/source/docs/providers/cloudstack/r/network_acl.html.markdown @@ -23,9 +23,12 @@ resource "cloudstack_network_acl" "default" { The following arguments are supported: -* `name` - (Required) The name of the ACL. Changing this forces a new resource to be created. -* `description` - (Optional) The description of the ACL. Changing this forces a new resource to be created. -* `vpc` - (Required) The name of the VPC to create this ACL for. Changing this forces a new resource to be created. +* `name` - (Required) The name of the ACL. Changing this forces a new resource + to be created. +* `description` - (Optional) The description of the ACL. Changing this forces a + new resource to be created. +* `vpc` - (Required) The name of the VPC to create this ACL for. Changing this + forces a new resource to be created. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown b/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown index 38f13520f7..fb6b0891fb 100644 --- a/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown +++ b/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown @@ -33,8 +33,12 @@ 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. +* `managed` - (Optional) USE WITH CAUTION! If enabled all the firewall rules for + this network ACL will be managed by this resource. This means it will delete + all firewall rules that are not in your config! (defaults false) + +* `rule` - (Optional) Can be specified multiple times. Each rule block supports + fields documented below. If `managed = false` at least one rule is required! The `rule` block supports: @@ -62,4 +66,4 @@ The `rule` block supports: The following attributes are exported: -* `aclid` - The ACL ID for which the rules are created. +* `ID` - The ACL ID for which the rules are created. diff --git a/website/source/layouts/cloudstack.erb b/website/source/layouts/cloudstack.erb index 4c0a7f5b53..a0e137aae3 100644 --- a/website/source/layouts/cloudstack.erb +++ b/website/source/layouts/cloudstack.erb @@ -17,6 +17,10 @@ cloudstack_disk +