github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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 := Retry(4, retryableACLCreationFunc(cs, p))
   161  		if err != nil {
   162  			return err
   163  		}
   164  
   165  		uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id
   166  		rule["uuids"] = uuids
   167  	}
   168  
   169  	// If the protocol is ALL set the needed parameters
   170  	if rule["protocol"].(string) == "all" {
   171  		r, err := Retry(4, retryableACLCreationFunc(cs, p))
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id
   177  		rule["uuids"] = uuids
   178  	}
   179  
   180  	// If protocol is TCP or UDP, loop through all ports
   181  	if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" {
   182  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   183  
   184  			// Create an empty schema.Set to hold all processed ports
   185  			ports := &schema.Set{
   186  				F: func(v interface{}) int {
   187  					return hashcode.String(v.(string))
   188  				},
   189  			}
   190  
   191  			for _, port := range ps.List() {
   192  				re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
   193  				m := re.FindStringSubmatch(port.(string))
   194  
   195  				startPort, err := strconv.Atoi(m[1])
   196  				if err != nil {
   197  					return err
   198  				}
   199  
   200  				endPort := startPort
   201  				if m[2] != "" {
   202  					endPort, err = strconv.Atoi(m[2])
   203  					if err != nil {
   204  						return err
   205  					}
   206  				}
   207  
   208  				p.SetStartport(startPort)
   209  				p.SetEndport(endPort)
   210  
   211  				r, err := Retry(4, retryableACLCreationFunc(cs, p))
   212  				if err != nil {
   213  					return err
   214  				}
   215  
   216  				ports.Add(port)
   217  				rule["ports"] = ports
   218  
   219  				uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id
   220  				rule["uuids"] = uuids
   221  			}
   222  		}
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error {
   229  	cs := meta.(*cloudstack.CloudStackClient)
   230  
   231  	// Get all the rules from the running environment
   232  	p := cs.NetworkACL.NewListNetworkACLsParams()
   233  	p.SetAclid(d.Id())
   234  	p.SetListall(true)
   235  
   236  	l, err := cs.NetworkACL.ListNetworkACLs(p)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	// Make a map of all the rules so we can easily find a rule
   242  	ruleMap := make(map[string]*cloudstack.NetworkACL, l.Count)
   243  	for _, r := range l.NetworkACLs {
   244  		ruleMap[r.Id] = r
   245  	}
   246  
   247  	// Create an empty schema.Set to hold all rules
   248  	rules := &schema.Set{
   249  		F: resourceCloudStackNetworkACLRuleHash,
   250  	}
   251  
   252  	// Read all rules that are configured
   253  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   254  		for _, rule := range rs.List() {
   255  			rule := rule.(map[string]interface{})
   256  			uuids := rule["uuids"].(map[string]interface{})
   257  
   258  			if rule["protocol"].(string) == "icmp" {
   259  				id, ok := uuids["icmp"]
   260  				if !ok {
   261  					continue
   262  				}
   263  
   264  				// Get the rule
   265  				r, ok := ruleMap[id.(string)]
   266  				if !ok {
   267  					delete(uuids, "icmp")
   268  					continue
   269  				}
   270  
   271  				// Delete the known rule so only unknown rules remain in the ruleMap
   272  				delete(ruleMap, id.(string))
   273  
   274  				// Update the values
   275  				rule["action"] = strings.ToLower(r.Action)
   276  				rule["source_cidr"] = r.Cidrlist
   277  				rule["protocol"] = r.Protocol
   278  				rule["icmp_type"] = r.Icmptype
   279  				rule["icmp_code"] = r.Icmpcode
   280  				rule["traffic_type"] = strings.ToLower(r.Traffictype)
   281  				rules.Add(rule)
   282  			}
   283  
   284  			if rule["protocol"].(string) == "all" {
   285  				id, ok := uuids["all"]
   286  				if !ok {
   287  					continue
   288  				}
   289  
   290  				// Get the rule
   291  				r, ok := ruleMap[id.(string)]
   292  				if !ok {
   293  					delete(uuids, "all")
   294  					continue
   295  				}
   296  
   297  				// Delete the known rule so only unknown rules remain in the ruleMap
   298  				delete(ruleMap, id.(string))
   299  
   300  				// Update the values
   301  				rule["action"] = strings.ToLower(r.Action)
   302  				rule["source_cidr"] = r.Cidrlist
   303  				rule["protocol"] = r.Protocol
   304  				rule["traffic_type"] = strings.ToLower(r.Traffictype)
   305  				rules.Add(rule)
   306  			}
   307  
   308  			// If protocol is tcp or udp, loop through all ports
   309  			if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" {
   310  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   311  
   312  					// Create an empty schema.Set to hold all ports
   313  					ports := &schema.Set{
   314  						F: func(v interface{}) int {
   315  							return hashcode.String(v.(string))
   316  						},
   317  					}
   318  
   319  					// Loop through all ports and retrieve their info
   320  					for _, port := range ps.List() {
   321  						id, ok := uuids[port.(string)]
   322  						if !ok {
   323  							continue
   324  						}
   325  
   326  						// Get the rule
   327  						r, ok := ruleMap[id.(string)]
   328  						if !ok {
   329  							delete(uuids, port.(string))
   330  							continue
   331  						}
   332  
   333  						// Delete the known rule so only unknown rules remain in the ruleMap
   334  						delete(ruleMap, id.(string))
   335  
   336  						// Update the values
   337  						rule["action"] = strings.ToLower(r.Action)
   338  						rule["source_cidr"] = r.Cidrlist
   339  						rule["protocol"] = r.Protocol
   340  						rule["traffic_type"] = strings.ToLower(r.Traffictype)
   341  						ports.Add(port)
   342  					}
   343  
   344  					// If there is at least one port found, add this rule to the rules set
   345  					if ports.Len() > 0 {
   346  						rule["ports"] = ports
   347  						rules.Add(rule)
   348  					}
   349  				}
   350  			}
   351  		}
   352  	}
   353  
   354  	// If this is a managed firewall, add all unknown rules into a single dummy rule
   355  	managed := d.Get("managed").(bool)
   356  	if managed && len(ruleMap) > 0 {
   357  		// Add all UUIDs to a uuids map
   358  		uuids := make(map[string]interface{}, len(ruleMap))
   359  		for uuid := range ruleMap {
   360  			uuids[uuid] = uuid
   361  		}
   362  
   363  		rule := map[string]interface{}{
   364  			"source_cidr": "N/A",
   365  			"protocol":    "N/A",
   366  			"uuids":       uuids,
   367  		}
   368  
   369  		// Add the dummy rule to the rules set
   370  		rules.Add(rule)
   371  	}
   372  
   373  	if rules.Len() > 0 {
   374  		d.Set("rule", rules)
   375  	} else if !managed {
   376  		d.SetId("")
   377  	}
   378  
   379  	return nil
   380  }
   381  
   382  func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error {
   383  	// Make sure all required parameters are there
   384  	if err := verifyNetworkACLParams(d); err != nil {
   385  		return err
   386  	}
   387  
   388  	// Check if the rule set as a whole has changed
   389  	if d.HasChange("rule") {
   390  		o, n := d.GetChange("rule")
   391  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   392  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   393  
   394  		// Now first loop through all the old rules and delete any obsolete ones
   395  		for _, rule := range ors.List() {
   396  			// Delete the rule as it no longer exists in the config
   397  			err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
   398  			if err != nil {
   399  				return err
   400  			}
   401  		}
   402  
   403  		// Make sure we save the state of the currently configured rules
   404  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   405  		d.Set("rule", rules)
   406  
   407  		// Then loop through all the currently configured rules and create the new ones
   408  		for _, rule := range nrs.List() {
   409  			// When successfully deleted, re-create it again if it still exists
   410  			err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{}))
   411  
   412  			// We need to update this first to preserve the correct state
   413  			rules.Add(rule)
   414  			d.Set("rule", rules)
   415  
   416  			if err != nil {
   417  				return err
   418  			}
   419  		}
   420  	}
   421  
   422  	return resourceCloudStackNetworkACLRuleRead(d, meta)
   423  }
   424  
   425  func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error {
   426  	// Delete all rules
   427  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   428  		for _, rule := range rs.List() {
   429  			// Delete a single rule
   430  			err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
   431  
   432  			// We need to update this first to preserve the correct state
   433  			d.Set("rule", rs)
   434  
   435  			if err != nil {
   436  				return err
   437  			}
   438  		}
   439  	}
   440  
   441  	return nil
   442  }
   443  
   444  func resourceCloudStackNetworkACLRuleDeleteRule(
   445  	d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   446  	cs := meta.(*cloudstack.CloudStackClient)
   447  	uuids := rule["uuids"].(map[string]interface{})
   448  
   449  	for k, id := range uuids {
   450  		// We don't care about the count here, so just continue
   451  		if k == "#" {
   452  			continue
   453  		}
   454  
   455  		// Create the parameter struct
   456  		p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string))
   457  
   458  		// Delete the rule
   459  		if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil {
   460  
   461  			// This is a very poor way to be told the ID does no longer exist :(
   462  			if strings.Contains(err.Error(), fmt.Sprintf(
   463  				"Invalid parameter id value=%s due to incorrect long value format, "+
   464  					"or entity does not exist", id.(string))) {
   465  				delete(uuids, k)
   466  				continue
   467  			}
   468  
   469  			return err
   470  		}
   471  
   472  		// Delete the UUID of this rule
   473  		delete(uuids, k)
   474  	}
   475  
   476  	// Update the UUIDs
   477  	rule["uuids"] = uuids
   478  
   479  	return nil
   480  }
   481  
   482  func resourceCloudStackNetworkACLRuleHash(v interface{}) int {
   483  	var buf bytes.Buffer
   484  	m := v.(map[string]interface{})
   485  
   486  	// This is a little ugly, but it's needed because these arguments have
   487  	// a default value that needs to be part of the string to hash
   488  	var action, trafficType string
   489  	if a, ok := m["action"]; ok {
   490  		action = a.(string)
   491  	} else {
   492  		action = "allow"
   493  	}
   494  	if t, ok := m["traffic_type"]; ok {
   495  		trafficType = t.(string)
   496  	} else {
   497  		trafficType = "ingress"
   498  	}
   499  
   500  	buf.WriteString(fmt.Sprintf(
   501  		"%s-%s-%s-%s-",
   502  		action,
   503  		m["source_cidr"].(string),
   504  		m["protocol"].(string),
   505  		trafficType))
   506  
   507  	if v, ok := m["icmp_type"]; ok {
   508  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   509  	}
   510  
   511  	if v, ok := m["icmp_code"]; ok {
   512  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   513  	}
   514  
   515  	// We need to make sure to sort the strings below so that we always
   516  	// generate the same hash code no matter what is in the set.
   517  	if v, ok := m["ports"]; ok {
   518  		vs := v.(*schema.Set).List()
   519  		s := make([]string, len(vs))
   520  
   521  		for i, raw := range vs {
   522  			s[i] = raw.(string)
   523  		}
   524  		sort.Strings(s)
   525  
   526  		for _, v := range s {
   527  			buf.WriteString(fmt.Sprintf("%s-", v))
   528  		}
   529  	}
   530  
   531  	return hashcode.String(buf.String())
   532  }
   533  
   534  func verifyNetworkACLParams(d *schema.ResourceData) error {
   535  	managed := d.Get("managed").(bool)
   536  	_, rules := d.GetOk("rule")
   537  
   538  	if !rules && !managed {
   539  		return fmt.Errorf(
   540  			"You must supply at least one 'rule' when not using the 'managed' firewall feature")
   541  	}
   542  
   543  	return nil
   544  }
   545  
   546  func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   547  	action := rule["action"].(string)
   548  	if action != "allow" && action != "deny" {
   549  		return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values")
   550  	}
   551  
   552  	protocol := rule["protocol"].(string)
   553  	switch protocol {
   554  	case "icmp":
   555  		if _, ok := rule["icmp_type"]; !ok {
   556  			return fmt.Errorf(
   557  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   558  		}
   559  		if _, ok := rule["icmp_code"]; !ok {
   560  			return fmt.Errorf(
   561  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   562  		}
   563  	case "all":
   564  		// No additional test are needed, so just leave this empty...
   565  	case "tcp", "udp":
   566  		if _, ok := rule["ports"]; !ok {
   567  			return fmt.Errorf(
   568  				"Parameter ports is a required parameter when *not* using protocol 'icmp'")
   569  		}
   570  	default:
   571  		_, err := strconv.ParseInt(protocol, 0, 0)
   572  		if err != nil {
   573  			return fmt.Errorf(
   574  				"%s is not a valid protocol. Valid options are 'tcp', 'udp', "+
   575  					"'icmp', 'all' or a valid protocol number", protocol)
   576  		}
   577  	}
   578  
   579  	traffic := rule["traffic_type"].(string)
   580  	if traffic != "ingress" && traffic != "egress" {
   581  		return fmt.Errorf(
   582  			"Parameter traffic_type only accepts 'ingress' or 'egress' as values")
   583  	}
   584  
   585  	return nil
   586  }
   587  
   588  func retryableACLCreationFunc(
   589  	cs *cloudstack.CloudStackClient,
   590  	p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) {
   591  	return func() (interface{}, error) {
   592  		r, err := cs.NetworkACL.CreateNetworkACL(p)
   593  		if err != nil {
   594  			return nil, err
   595  		}
   596  		return r, nil
   597  	}
   598  }