mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
These fixes are needed to make the provider work with master again. These are still some issues, but they seem not to be related to the provider, but the changes in `helper/schema`.
486 lines
12 KiB
Go
486 lines
12 KiB
Go
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,
|
|
},
|
|
|
|
"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 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(ipaddressid)
|
|
|
|
// 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, 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{}, 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 := verifyFirewallRuleParams(d, rule); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a new parameter struct
|
|
p := cs.Firewall.NewCreateFirewallRuleParams(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.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 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 {
|
|
d.SetId("")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceCloudStackFirewallUpdate(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 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 {
|
|
// We don't care about the count here, so just continue
|
|
if k == "#" {
|
|
continue
|
|
}
|
|
|
|
// 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 verifyFirewallRuleParams(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
|
|
}
|