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