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