github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"regexp"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/terraform/helper/hashcode"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/xanzy/go-cloudstack/cloudstack"
    14  )
    15  
    16  func resourceCloudStackNetworkACLRule() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceCloudStackNetworkACLRuleCreate,
    19  		Read:   resourceCloudStackNetworkACLRuleRead,
    20  		Update: resourceCloudStackNetworkACLRuleUpdate,
    21  		Delete: resourceCloudStackNetworkACLRuleDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"aclid": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"rule": &schema.Schema{
    31  				Type:     schema.TypeSet,
    32  				Required: true,
    33  				Elem: &schema.Resource{
    34  					Schema: map[string]*schema.Schema{
    35  						"action": &schema.Schema{
    36  							Type:     schema.TypeString,
    37  							Optional: true,
    38  							Default:  "allow",
    39  						},
    40  
    41  						"source_cidr": &schema.Schema{
    42  							Type:     schema.TypeString,
    43  							Required: true,
    44  						},
    45  
    46  						"protocol": &schema.Schema{
    47  							Type:     schema.TypeString,
    48  							Required: true,
    49  						},
    50  
    51  						"icmp_type": &schema.Schema{
    52  							Type:     schema.TypeInt,
    53  							Optional: true,
    54  							Computed: true,
    55  						},
    56  
    57  						"icmp_code": &schema.Schema{
    58  							Type:     schema.TypeInt,
    59  							Optional: true,
    60  							Computed: true,
    61  						},
    62  
    63  						"ports": &schema.Schema{
    64  							Type:     schema.TypeSet,
    65  							Optional: true,
    66  							Elem:     &schema.Schema{Type: schema.TypeString},
    67  							Set: func(v interface{}) int {
    68  								return hashcode.String(v.(string))
    69  							},
    70  						},
    71  
    72  						"traffic_type": &schema.Schema{
    73  							Type:     schema.TypeString,
    74  							Optional: true,
    75  							Default:  "ingress",
    76  						},
    77  
    78  						"uuids": &schema.Schema{
    79  							Type:     schema.TypeMap,
    80  							Computed: true,
    81  						},
    82  					},
    83  				},
    84  				Set: resourceCloudStackNetworkACLRuleHash,
    85  			},
    86  		},
    87  	}
    88  }
    89  
    90  func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error {
    91  	// Get the acl UUID
    92  	aclid := d.Get("aclid").(string)
    93  
    94  	// We need to set this upfront in order to be able to save a partial state
    95  	d.SetId(aclid)
    96  
    97  	// Create all rules that are configured
    98  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
    99  
   100  		// Create an empty schema.Set to hold all rules
   101  		rules := &schema.Set{
   102  			F: resourceCloudStackNetworkACLRuleHash,
   103  		}
   104  
   105  		for _, rule := range rs.List() {
   106  			// Create a single rule
   107  			err := resourceCloudStackNetworkACLRuleCreateRule(
   108  				d, meta, aclid, rule.(map[string]interface{}))
   109  
   110  			// We need to update this first to preserve the correct state
   111  			rules.Add(rule)
   112  			d.Set("rule", rules)
   113  
   114  			if err != nil {
   115  				return err
   116  			}
   117  		}
   118  	}
   119  
   120  	return resourceCloudStackNetworkACLRuleRead(d, meta)
   121  }
   122  
   123  func resourceCloudStackNetworkACLRuleCreateRule(
   124  	d *schema.ResourceData, meta interface{}, aclid string, rule map[string]interface{}) error {
   125  	cs := meta.(*cloudstack.CloudStackClient)
   126  	uuids := rule["uuids"].(map[string]interface{})
   127  
   128  	// Make sure all required parameters are there
   129  	if err := verifyNetworkACLRuleParams(d, rule); err != nil {
   130  		return err
   131  	}
   132  
   133  	// Create a new parameter struct
   134  	p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string))
   135  
   136  	// Set the acl ID
   137  	p.SetAclid(aclid)
   138  
   139  	// Set the action
   140  	p.SetAction(rule["action"].(string))
   141  
   142  	// Set the CIDR list
   143  	p.SetCidrlist([]string{rule["source_cidr"].(string)})
   144  
   145  	// Set the traffic type
   146  	p.SetTraffictype(rule["traffic_type"].(string))
   147  
   148  	// If the protocol is ICMP set the needed ICMP parameters
   149  	if rule["protocol"].(string) == "icmp" {
   150  		p.SetIcmptype(rule["icmp_type"].(int))
   151  		p.SetIcmpcode(rule["icmp_code"].(int))
   152  
   153  		r, err := cs.NetworkACL.CreateNetworkACL(p)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		uuids["icmp"] = r.Id
   158  		rule["uuids"] = uuids
   159  	}
   160  
   161  	// If protocol is not ICMP, loop through all ports
   162  	if rule["protocol"].(string) != "icmp" {
   163  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   164  
   165  			// Create an empty schema.Set to hold all processed ports
   166  			ports := &schema.Set{
   167  				F: func(v interface{}) int {
   168  					return hashcode.String(v.(string))
   169  				},
   170  			}
   171  
   172  			for _, port := range ps.List() {
   173  				re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
   174  				m := re.FindStringSubmatch(port.(string))
   175  
   176  				startPort, err := strconv.Atoi(m[1])
   177  				if err != nil {
   178  					return err
   179  				}
   180  
   181  				endPort := startPort
   182  				if m[2] != "" {
   183  					endPort, err = strconv.Atoi(m[2])
   184  					if err != nil {
   185  						return err
   186  					}
   187  				}
   188  
   189  				p.SetStartport(startPort)
   190  				p.SetEndport(endPort)
   191  
   192  				r, err := cs.NetworkACL.CreateNetworkACL(p)
   193  				if err != nil {
   194  					return err
   195  				}
   196  
   197  				ports.Add(port)
   198  				rule["ports"] = ports
   199  
   200  				uuids[port.(string)] = r.Id
   201  				rule["uuids"] = uuids
   202  			}
   203  		}
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error {
   210  	cs := meta.(*cloudstack.CloudStackClient)
   211  
   212  	// Create an empty schema.Set to hold all rules
   213  	rules := &schema.Set{
   214  		F: resourceCloudStackNetworkACLRuleHash,
   215  	}
   216  
   217  	// Read all rules that are configured
   218  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   219  		for _, rule := range rs.List() {
   220  			rule := rule.(map[string]interface{})
   221  			uuids := rule["uuids"].(map[string]interface{})
   222  
   223  			if rule["protocol"].(string) == "icmp" {
   224  				id, ok := uuids["icmp"]
   225  				if !ok {
   226  					continue
   227  				}
   228  
   229  				// Get the rule
   230  				r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
   231  				// If the count == 0, there is no object found for this UUID
   232  				if err != nil {
   233  					if count == 0 {
   234  						delete(uuids, "icmp")
   235  						continue
   236  					}
   237  
   238  					return err
   239  				}
   240  
   241  				// Update the values
   242  				rule["action"] = r.Action
   243  				rule["source_cidr"] = r.Cidrlist
   244  				rule["protocol"] = r.Protocol
   245  				rule["icmp_type"] = r.Icmptype
   246  				rule["icmp_code"] = r.Icmpcode
   247  				rule["traffic_type"] = r.Traffictype
   248  				rules.Add(rule)
   249  			}
   250  
   251  			// If protocol is not ICMP, loop through all ports
   252  			if rule["protocol"].(string) != "icmp" {
   253  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   254  
   255  					// Create an empty schema.Set to hold all ports
   256  					ports := &schema.Set{
   257  						F: func(v interface{}) int {
   258  							return hashcode.String(v.(string))
   259  						},
   260  					}
   261  
   262  					// Loop through all ports and retrieve their info
   263  					for _, port := range ps.List() {
   264  						id, ok := uuids[port.(string)]
   265  						if !ok {
   266  							continue
   267  						}
   268  
   269  						// Get the rule
   270  						r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
   271  						if err != nil {
   272  							if count == 0 {
   273  								delete(uuids, port.(string))
   274  								continue
   275  							}
   276  
   277  							return err
   278  						}
   279  
   280  						// Update the values
   281  						rule["action"] = strings.ToLower(r.Action)
   282  						rule["source_cidr"] = r.Cidrlist
   283  						rule["protocol"] = r.Protocol
   284  						rule["traffic_type"] = strings.ToLower(r.Traffictype)
   285  						ports.Add(port)
   286  					}
   287  
   288  					// If there is at least one port found, add this rule to the rules set
   289  					if ports.Len() > 0 {
   290  						rule["ports"] = ports
   291  						rules.Add(rule)
   292  					}
   293  				}
   294  			}
   295  		}
   296  	}
   297  
   298  	if rules.Len() > 0 {
   299  		d.Set("rule", rules)
   300  	} else {
   301  		d.SetId("")
   302  	}
   303  
   304  	return nil
   305  }
   306  
   307  func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error {
   308  	// Get the acl UUID
   309  	aclid := d.Get("aclid").(string)
   310  
   311  	// Check if the rule set as a whole has changed
   312  	if d.HasChange("rule") {
   313  		o, n := d.GetChange("rule")
   314  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   315  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   316  
   317  		// Now first loop through all the old rules and delete any obsolete ones
   318  		for _, rule := range ors.List() {
   319  			// Delete the rule as it no longer exists in the config
   320  			err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
   321  			if err != nil {
   322  				return err
   323  			}
   324  		}
   325  
   326  		// Make sure we save the state of the currently configured rules
   327  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   328  		d.Set("rule", rules)
   329  
   330  		// Then loop through al the currently configured rules and create the new ones
   331  		for _, rule := range nrs.List() {
   332  			// When succesfully deleted, re-create it again if it still exists
   333  			err := resourceCloudStackNetworkACLRuleCreateRule(
   334  				d, meta, aclid, rule.(map[string]interface{}))
   335  
   336  			// We need to update this first to preserve the correct state
   337  			rules.Add(rule)
   338  			d.Set("rule", rules)
   339  
   340  			if err != nil {
   341  				return err
   342  			}
   343  		}
   344  	}
   345  
   346  	return resourceCloudStackNetworkACLRuleRead(d, meta)
   347  }
   348  
   349  func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error {
   350  	// Delete all rules
   351  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   352  		for _, rule := range rs.List() {
   353  			// Delete a single rule
   354  			err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
   355  
   356  			// We need to update this first to preserve the correct state
   357  			d.Set("rule", rs)
   358  
   359  			if err != nil {
   360  				return err
   361  			}
   362  		}
   363  	}
   364  
   365  	return nil
   366  }
   367  
   368  func resourceCloudStackNetworkACLRuleDeleteRule(
   369  	d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   370  	cs := meta.(*cloudstack.CloudStackClient)
   371  	uuids := rule["uuids"].(map[string]interface{})
   372  
   373  	for k, id := range uuids {
   374  		// Create the parameter struct
   375  		p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string))
   376  
   377  		// Delete the rule
   378  		if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil {
   379  
   380  			// This is a very poor way to be told the UUID does no longer exist :(
   381  			if strings.Contains(err.Error(), fmt.Sprintf(
   382  				"Invalid parameter id value=%s due to incorrect long value format, "+
   383  					"or entity does not exist", id.(string))) {
   384  				delete(uuids, k)
   385  				continue
   386  			}
   387  
   388  			return err
   389  		}
   390  
   391  		// Delete the UUID of this rule
   392  		delete(uuids, k)
   393  	}
   394  
   395  	// Update the UUIDs
   396  	rule["uuids"] = uuids
   397  
   398  	return nil
   399  }
   400  
   401  func resourceCloudStackNetworkACLRuleHash(v interface{}) int {
   402  	var buf bytes.Buffer
   403  	m := v.(map[string]interface{})
   404  	buf.WriteString(fmt.Sprintf(
   405  		"%s-%s-%s-%s-",
   406  		m["action"].(string),
   407  		m["source_cidr"].(string),
   408  		m["protocol"].(string),
   409  		m["traffic_type"].(string)))
   410  
   411  	if v, ok := m["icmp_type"]; ok {
   412  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   413  	}
   414  
   415  	if v, ok := m["icmp_code"]; ok {
   416  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   417  	}
   418  
   419  	// We need to make sure to sort the strings below so that we always
   420  	// generate the same hash code no matter what is in the set.
   421  	if v, ok := m["ports"]; ok {
   422  		vs := v.(*schema.Set).List()
   423  		s := make([]string, len(vs))
   424  
   425  		for i, raw := range vs {
   426  			s[i] = raw.(string)
   427  		}
   428  		sort.Strings(s)
   429  
   430  		for _, v := range s {
   431  			buf.WriteString(fmt.Sprintf("%s-", v))
   432  		}
   433  	}
   434  
   435  	return hashcode.String(buf.String())
   436  }
   437  
   438  func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   439  	action := rule["action"].(string)
   440  	if action != "allow" && action != "deny" {
   441  		return fmt.Errorf("Parameter action only excepts 'allow' or 'deny' as values")
   442  	}
   443  
   444  	protocol := rule["protocol"].(string)
   445  	if protocol == "icmp" {
   446  		if _, ok := rule["icmp_type"]; !ok {
   447  			return fmt.Errorf(
   448  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   449  		}
   450  		if _, ok := rule["icmp_code"]; !ok {
   451  			return fmt.Errorf(
   452  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   453  		}
   454  	} else {
   455  		if protocol != "tcp" && protocol != "udp" && protocol != "all" {
   456  			_, err := strconv.ParseInt(protocol, 0, 0)
   457  			if err != nil {
   458  				return fmt.Errorf(
   459  					"%s is not a valid protocol. Valid options are 'tcp', 'udp', "+
   460  						"'icmp', 'all' or a valid protocol number", protocol)
   461  			}
   462  		}
   463  		if _, ok := rule["ports"]; !ok {
   464  			return fmt.Errorf(
   465  				"Parameter ports is a required parameter when *not* using protocol 'icmp'")
   466  		}
   467  	}
   468  
   469  	traffic := rule["traffic_type"].(string)
   470  	if traffic != "ingress" && traffic != "egress" {
   471  		return fmt.Errorf(
   472  			"Parameter traffic_type only excepts 'ingress' or 'egress' as values")
   473  	}
   474  
   475  	return nil
   476  }