github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.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 resourceCloudStackEgressFirewall() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceCloudStackEgressFirewallCreate,
    19  		Read:   resourceCloudStackEgressFirewallRead,
    20  		Update: resourceCloudStackEgressFirewallUpdate,
    21  		Delete: resourceCloudStackEgressFirewallDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"network": &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  						"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  						"uuids": &schema.Schema{
    73  							Type:     schema.TypeMap,
    74  							Computed: true,
    75  						},
    76  					},
    77  				},
    78  				Set: resourceCloudStackEgressFirewallRuleHash,
    79  			},
    80  		},
    81  	}
    82  }
    83  
    84  func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error {
    85  	cs := meta.(*cloudstack.CloudStackClient)
    86  
    87  	// Make sure all required parameters are there
    88  	if err := verifyEgressFirewallParams(d); err != nil {
    89  		return err
    90  	}
    91  
    92  	// Retrieve the network ID
    93  	networkid, e := retrieveID(cs, "network", d.Get("network").(string))
    94  	if e != nil {
    95  		return e.Error()
    96  	}
    97  
    98  	// We need to set this upfront in order to be able to save a partial state
    99  	d.SetId(networkid)
   100  
   101  	// Create all rules that are configured
   102  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   103  
   104  		// Create an empty schema.Set to hold all rules
   105  		rules := &schema.Set{
   106  			F: resourceCloudStackEgressFirewallRuleHash,
   107  		}
   108  
   109  		for _, rule := range rs.List() {
   110  			// Create a single rule
   111  			err := resourceCloudStackEgressFirewallCreateRule(d, meta, rule.(map[string]interface{}))
   112  
   113  			// We need to update this first to preserve the correct state
   114  			rules.Add(rule)
   115  			d.Set("rule", rules)
   116  
   117  			if err != nil {
   118  				return err
   119  			}
   120  		}
   121  	}
   122  
   123  	return resourceCloudStackEgressFirewallRead(d, meta)
   124  }
   125  
   126  func resourceCloudStackEgressFirewallCreateRule(
   127  	d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   128  	cs := meta.(*cloudstack.CloudStackClient)
   129  	uuids := rule["uuids"].(map[string]interface{})
   130  
   131  	// Make sure all required rule parameters are there
   132  	if err := verifyEgressFirewallRuleParams(d, rule); err != nil {
   133  		return err
   134  	}
   135  
   136  	// Create a new parameter struct
   137  	p := cs.Firewall.NewCreateEgressFirewallRuleParams(d.Id(), rule["protocol"].(string))
   138  
   139  	// Set the CIDR list
   140  	p.SetCidrlist([]string{rule["source_cidr"].(string)})
   141  
   142  	// If the protocol is ICMP set the needed ICMP parameters
   143  	if rule["protocol"].(string) == "icmp" {
   144  		p.SetIcmptype(rule["icmp_type"].(int))
   145  		p.SetIcmpcode(rule["icmp_code"].(int))
   146  
   147  		r, err := cs.Firewall.CreateEgressFirewallRule(p)
   148  		if err != nil {
   149  			return err
   150  		}
   151  		uuids["icmp"] = r.Id
   152  		rule["uuids"] = uuids
   153  	}
   154  
   155  	// If protocol is not ICMP, loop through all ports
   156  	if rule["protocol"].(string) != "icmp" {
   157  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   158  
   159  			// Create an empty schema.Set to hold all processed ports
   160  			ports := &schema.Set{
   161  				F: func(v interface{}) int {
   162  					return hashcode.String(v.(string))
   163  				},
   164  			}
   165  
   166  			for _, port := range ps.List() {
   167  				re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
   168  				m := re.FindStringSubmatch(port.(string))
   169  
   170  				startPort, err := strconv.Atoi(m[1])
   171  				if err != nil {
   172  					return err
   173  				}
   174  
   175  				endPort := startPort
   176  				if m[2] != "" {
   177  					endPort, err = strconv.Atoi(m[2])
   178  					if err != nil {
   179  						return err
   180  					}
   181  				}
   182  
   183  				p.SetStartport(startPort)
   184  				p.SetEndport(endPort)
   185  
   186  				r, err := cs.Firewall.CreateEgressFirewallRule(p)
   187  				if err != nil {
   188  					return err
   189  				}
   190  
   191  				ports.Add(port)
   192  				rule["ports"] = ports
   193  
   194  				uuids[port.(string)] = r.Id
   195  				rule["uuids"] = uuids
   196  			}
   197  		}
   198  	}
   199  
   200  	return nil
   201  }
   202  
   203  func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error {
   204  	cs := meta.(*cloudstack.CloudStackClient)
   205  
   206  	// Create an empty schema.Set to hold all rules
   207  	rules := &schema.Set{
   208  		F: resourceCloudStackEgressFirewallRuleHash,
   209  	}
   210  
   211  	// Read all rules that are configured
   212  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   213  		for _, rule := range rs.List() {
   214  			rule := rule.(map[string]interface{})
   215  			uuids := rule["uuids"].(map[string]interface{})
   216  
   217  			if rule["protocol"].(string) == "icmp" {
   218  				id, ok := uuids["icmp"]
   219  				if !ok {
   220  					continue
   221  				}
   222  
   223  				// Get the rule
   224  				r, count, err := cs.Firewall.GetEgressFirewallRuleByID(id.(string))
   225  				// If the count == 0, there is no object found for this ID
   226  				if err != nil {
   227  					if count == 0 {
   228  						delete(uuids, "icmp")
   229  						continue
   230  					}
   231  
   232  					return err
   233  				}
   234  
   235  				// Update the values
   236  				rule["source_cidr"] = r.Cidrlist
   237  				rule["protocol"] = r.Protocol
   238  				rule["icmp_type"] = r.Icmptype
   239  				rule["icmp_code"] = r.Icmpcode
   240  				rules.Add(rule)
   241  			}
   242  
   243  			// If protocol is not ICMP, loop through all ports
   244  			if rule["protocol"].(string) != "icmp" {
   245  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   246  
   247  					// Create an empty schema.Set to hold all ports
   248  					ports := &schema.Set{
   249  						F: func(v interface{}) int {
   250  							return hashcode.String(v.(string))
   251  						},
   252  					}
   253  
   254  					// Loop through all ports and retrieve their info
   255  					for _, port := range ps.List() {
   256  						id, ok := uuids[port.(string)]
   257  						if !ok {
   258  							continue
   259  						}
   260  
   261  						// Get the rule
   262  						r, count, err := cs.Firewall.GetEgressFirewallRuleByID(id.(string))
   263  						if err != nil {
   264  							if count == 0 {
   265  								delete(uuids, port.(string))
   266  								continue
   267  							}
   268  
   269  							return err
   270  						}
   271  
   272  						// Update the values
   273  						rule["source_cidr"] = r.Cidrlist
   274  						rule["protocol"] = r.Protocol
   275  						ports.Add(port)
   276  					}
   277  
   278  					// If there is at least one port found, add this rule to the rules set
   279  					if ports.Len() > 0 {
   280  						rule["ports"] = ports
   281  						rules.Add(rule)
   282  					}
   283  				}
   284  			}
   285  		}
   286  	}
   287  
   288  	// If this is a managed firewall, add all unknown rules into a single dummy rule
   289  	managed := d.Get("managed").(bool)
   290  	if managed {
   291  		// Get all the rules from the running environment
   292  		p := cs.Firewall.NewListEgressFirewallRulesParams()
   293  		p.SetNetworkid(d.Id())
   294  		p.SetListall(true)
   295  
   296  		r, err := cs.Firewall.ListEgressFirewallRules(p)
   297  		if err != nil {
   298  			return err
   299  		}
   300  
   301  		// Add all UUIDs to the uuids map
   302  		uuids := make(map[string]interface{}, len(r.EgressFirewallRules))
   303  		for _, r := range r.EgressFirewallRules {
   304  			uuids[r.Id] = r.Id
   305  		}
   306  
   307  		// Delete all expected UUIDs from the uuids map
   308  		for _, rule := range rules.List() {
   309  			rule := rule.(map[string]interface{})
   310  
   311  			for _, id := range rule["uuids"].(map[string]interface{}) {
   312  				delete(uuids, id.(string))
   313  			}
   314  		}
   315  
   316  		if len(uuids) > 0 {
   317  			// Make a dummy rule to hold all unknown UUIDs
   318  			rule := map[string]interface{}{
   319  				"source_cidr": "N/A",
   320  				"protocol":    "N/A",
   321  				"uuids":       uuids,
   322  			}
   323  
   324  			// Add the dummy rule to the rules set
   325  			rules.Add(rule)
   326  		}
   327  	}
   328  
   329  	if rules.Len() > 0 {
   330  		d.Set("rule", rules)
   331  	} else if !managed {
   332  		d.SetId("")
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
   339  	// Make sure all required parameters are there
   340  	if err := verifyEgressFirewallParams(d); err != nil {
   341  		return err
   342  	}
   343  
   344  	// Check if the rule set as a whole has changed
   345  	if d.HasChange("rule") {
   346  		o, n := d.GetChange("rule")
   347  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   348  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   349  
   350  		// Now first loop through all the old rules and delete any obsolete ones
   351  		for _, rule := range ors.List() {
   352  			// Delete the rule as it no longer exists in the config
   353  			err := resourceCloudStackEgressFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
   354  			if err != nil {
   355  				return err
   356  			}
   357  		}
   358  
   359  		// Make sure we save the state of the currently configured rules
   360  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   361  		d.Set("rule", rules)
   362  
   363  		// Then loop through all the currently configured rules and create the new ones
   364  		for _, rule := range nrs.List() {
   365  			// When successfully deleted, re-create it again if it still exists
   366  			err := resourceCloudStackEgressFirewallCreateRule(
   367  				d, meta, rule.(map[string]interface{}))
   368  
   369  			// We need to update this first to preserve the correct state
   370  			rules.Add(rule)
   371  			d.Set("rule", rules)
   372  
   373  			if err != nil {
   374  				return err
   375  			}
   376  		}
   377  	}
   378  
   379  	return resourceCloudStackEgressFirewallRead(d, meta)
   380  }
   381  
   382  func resourceCloudStackEgressFirewallDelete(d *schema.ResourceData, meta interface{}) error {
   383  	// Delete all rules
   384  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   385  		for _, rule := range rs.List() {
   386  			// Delete a single rule
   387  			err := resourceCloudStackEgressFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
   388  
   389  			// We need to update this first to preserve the correct state
   390  			d.Set("rule", rs)
   391  
   392  			if err != nil {
   393  				return err
   394  			}
   395  		}
   396  	}
   397  
   398  	return nil
   399  }
   400  
   401  func resourceCloudStackEgressFirewallDeleteRule(
   402  	d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   403  	cs := meta.(*cloudstack.CloudStackClient)
   404  	uuids := rule["uuids"].(map[string]interface{})
   405  
   406  	for k, id := range uuids {
   407  		// We don't care about the count here, so just continue
   408  		if k == "#" {
   409  			continue
   410  		}
   411  
   412  		// Create the parameter struct
   413  		p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string))
   414  
   415  		// Delete the rule
   416  		if _, err := cs.Firewall.DeleteEgressFirewallRule(p); err != nil {
   417  
   418  			// This is a very poor way to be told the ID does no longer exist :(
   419  			if strings.Contains(err.Error(), fmt.Sprintf(
   420  				"Invalid parameter id value=%s due to incorrect long value format, "+
   421  					"or entity does not exist", id.(string))) {
   422  				delete(uuids, k)
   423  				continue
   424  			}
   425  
   426  			return err
   427  		}
   428  
   429  		// Delete the UUID of this rule
   430  		delete(uuids, k)
   431  	}
   432  
   433  	// Update the UUIDs
   434  	rule["uuids"] = uuids
   435  
   436  	return nil
   437  }
   438  
   439  func resourceCloudStackEgressFirewallRuleHash(v interface{}) int {
   440  	var buf bytes.Buffer
   441  	m := v.(map[string]interface{})
   442  	buf.WriteString(fmt.Sprintf(
   443  		"%s-%s-", m["source_cidr"].(string), m["protocol"].(string)))
   444  
   445  	if v, ok := m["icmp_type"]; ok {
   446  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   447  	}
   448  
   449  	if v, ok := m["icmp_code"]; ok {
   450  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   451  	}
   452  
   453  	// We need to make sure to sort the strings below so that we always
   454  	// generate the same hash code no matter what is in the set.
   455  	if v, ok := m["ports"]; ok {
   456  		vs := v.(*schema.Set).List()
   457  		s := make([]string, len(vs))
   458  
   459  		for i, raw := range vs {
   460  			s[i] = raw.(string)
   461  		}
   462  		sort.Strings(s)
   463  
   464  		for _, v := range s {
   465  			buf.WriteString(fmt.Sprintf("%s-", v))
   466  		}
   467  	}
   468  
   469  	return hashcode.String(buf.String())
   470  }
   471  
   472  func verifyEgressFirewallParams(d *schema.ResourceData) error {
   473  	managed := d.Get("managed").(bool)
   474  	_, rules := d.GetOk("rule")
   475  
   476  	if !rules && !managed {
   477  		return fmt.Errorf(
   478  			"You must supply at least one 'rule' when not using the 'managed' firewall feature")
   479  	}
   480  
   481  	return nil
   482  }
   483  
   484  func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   485  	protocol := rule["protocol"].(string)
   486  	if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
   487  		return fmt.Errorf(
   488  			"%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
   489  	}
   490  
   491  	if protocol == "icmp" {
   492  		if _, ok := rule["icmp_type"]; !ok {
   493  			return fmt.Errorf(
   494  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   495  		}
   496  		if _, ok := rule["icmp_code"]; !ok {
   497  			return fmt.Errorf(
   498  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   499  		}
   500  	} else {
   501  		if _, ok := rule["ports"]; !ok {
   502  			return fmt.Errorf(
   503  				"Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
   504  		}
   505  	}
   506  
   507  	return nil
   508  }