mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-13 19:01:09 -04:00
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.
477 lines
12 KiB
Go
477 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 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
|
|
}
|