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