github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/cloudstack/resource_cloudstack_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 resourceCloudStackFirewall() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceCloudStackFirewallCreate,
    19  		Read:   resourceCloudStackFirewallRead,
    20  		Update: resourceCloudStackFirewallUpdate,
    21  		Delete: resourceCloudStackFirewallDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"ipaddress": &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: resourceCloudStackFirewallRuleHash,
    79  			},
    80  		},
    81  	}
    82  }
    83  
    84  func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{}) error {
    85  	cs := meta.(*cloudstack.CloudStackClient)
    86  
    87  	// Make sure all required parameters are there
    88  	if err := verifyFirewallParams(d); err != nil {
    89  		return err
    90  	}
    91  
    92  	// Retrieve the ipaddress ID
    93  	ipaddressid, e := retrieveID(cs, "ipaddress", d.Get("ipaddress").(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(ipaddressid)
   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: resourceCloudStackFirewallRuleHash,
   107  		}
   108  
   109  		for _, rule := range rs.List() {
   110  			// Create a single rule
   111  			err := resourceCloudStackFirewallCreateRule(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 resourceCloudStackFirewallRead(d, meta)
   124  }
   125  
   126  func resourceCloudStackFirewallCreateRule(
   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 := verifyFirewallRuleParams(d, rule); err != nil {
   133  		return err
   134  	}
   135  
   136  	// Create a new parameter struct
   137  	p := cs.Firewall.NewCreateFirewallRuleParams(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.CreateFirewallRule(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.CreateFirewallRule(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 resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) error {
   204  	cs := meta.(*cloudstack.CloudStackClient)
   205  
   206  	// Get all the rules from the running environment
   207  	p := cs.Firewall.NewListFirewallRulesParams()
   208  	p.SetIpaddressid(d.Id())
   209  	p.SetListall(true)
   210  
   211  	l, err := cs.Firewall.ListFirewallRules(p)
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	// Make a map of all the rules so we can easily find a rule
   217  	ruleMap := make(map[string]*cloudstack.FirewallRule, l.Count)
   218  	for _, r := range l.FirewallRules {
   219  		ruleMap[r.Id] = r
   220  	}
   221  
   222  	// Create an empty schema.Set to hold all rules
   223  	rules := &schema.Set{
   224  		F: resourceCloudStackFirewallRuleHash,
   225  	}
   226  
   227  	// Read all rules that are configured
   228  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   229  		for _, rule := range rs.List() {
   230  			rule := rule.(map[string]interface{})
   231  			uuids := rule["uuids"].(map[string]interface{})
   232  
   233  			if rule["protocol"].(string) == "icmp" {
   234  				id, ok := uuids["icmp"]
   235  				if !ok {
   236  					continue
   237  				}
   238  
   239  				// Get the rule
   240  				r, ok := ruleMap[id.(string)]
   241  				if !ok {
   242  					delete(uuids, "icmp")
   243  					continue
   244  				}
   245  
   246  				// Delete the known rule so only unknown rules remain in the ruleMap
   247  				delete(ruleMap, id.(string))
   248  
   249  				// Update the values
   250  				rule["source_cidr"] = r.Cidrlist
   251  				rule["protocol"] = r.Protocol
   252  				rule["icmp_type"] = r.Icmptype
   253  				rule["icmp_code"] = r.Icmpcode
   254  				rules.Add(rule)
   255  			}
   256  
   257  			// If protocol is not ICMP, loop through all ports
   258  			if rule["protocol"].(string) != "icmp" {
   259  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   260  
   261  					// Create an empty schema.Set to hold all ports
   262  					ports := &schema.Set{
   263  						F: func(v interface{}) int {
   264  							return hashcode.String(v.(string))
   265  						},
   266  					}
   267  
   268  					// Loop through all ports and retrieve their info
   269  					for _, port := range ps.List() {
   270  						id, ok := uuids[port.(string)]
   271  						if !ok {
   272  							continue
   273  						}
   274  
   275  						// Get the rule
   276  						r, ok := ruleMap[id.(string)]
   277  						if !ok {
   278  							delete(uuids, port.(string))
   279  							continue
   280  						}
   281  
   282  						// Delete the known rule so only unknown rules remain in the ruleMap
   283  						delete(ruleMap, id.(string))
   284  
   285  						// Update the values
   286  						rule["source_cidr"] = r.Cidrlist
   287  						rule["protocol"] = r.Protocol
   288  						ports.Add(port)
   289  					}
   290  
   291  					// If there is at least one port found, add this rule to the rules set
   292  					if ports.Len() > 0 {
   293  						rule["ports"] = ports
   294  						rules.Add(rule)
   295  					}
   296  				}
   297  			}
   298  		}
   299  	}
   300  
   301  	// If this is a managed firewall, add all unknown rules into a single dummy rule
   302  	managed := d.Get("managed").(bool)
   303  	if managed && len(ruleMap) > 0 {
   304  		// Add all UUIDs to a uuids map
   305  		uuids := make(map[string]interface{}, len(ruleMap))
   306  		for uuid := range ruleMap {
   307  			uuids[uuid] = uuid
   308  		}
   309  
   310  		// Make a dummy rule to hold all unknown UUIDs
   311  		rule := map[string]interface{}{
   312  			"source_cidr": "N/A",
   313  			"protocol":    "N/A",
   314  			"uuids":       uuids,
   315  		}
   316  
   317  		// Add the dummy rule to the rules set
   318  		rules.Add(rule)
   319  	}
   320  
   321  	if rules.Len() > 0 {
   322  		d.Set("rule", rules)
   323  	} else if !managed {
   324  		d.SetId("")
   325  	}
   326  
   327  	return nil
   328  }
   329  
   330  func resourceCloudStackFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
   331  	// Make sure all required parameters are there
   332  	if err := verifyFirewallParams(d); err != nil {
   333  		return err
   334  	}
   335  
   336  	// Check if the rule set as a whole has changed
   337  	if d.HasChange("rule") {
   338  		o, n := d.GetChange("rule")
   339  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   340  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   341  
   342  		// Now first loop through all the old rules and delete any obsolete ones
   343  		for _, rule := range ors.List() {
   344  			// Delete the rule as it no longer exists in the config
   345  			err := resourceCloudStackFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
   346  			if err != nil {
   347  				return err
   348  			}
   349  		}
   350  
   351  		// Make sure we save the state of the currently configured rules
   352  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   353  		d.Set("rule", rules)
   354  
   355  		// Then loop through all the currently configured rules and create the new ones
   356  		for _, rule := range nrs.List() {
   357  			// When successfully deleted, re-create it again if it still exists
   358  			err := resourceCloudStackFirewallCreateRule(
   359  				d, meta, rule.(map[string]interface{}))
   360  
   361  			// We need to update this first to preserve the correct state
   362  			rules.Add(rule)
   363  			d.Set("rule", rules)
   364  
   365  			if err != nil {
   366  				return err
   367  			}
   368  		}
   369  	}
   370  
   371  	return resourceCloudStackFirewallRead(d, meta)
   372  }
   373  
   374  func resourceCloudStackFirewallDelete(d *schema.ResourceData, meta interface{}) error {
   375  	// Delete all rules
   376  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   377  		for _, rule := range rs.List() {
   378  			// Delete a single rule
   379  			err := resourceCloudStackFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
   380  
   381  			// We need to update this first to preserve the correct state
   382  			d.Set("rule", rs)
   383  
   384  			if err != nil {
   385  				return err
   386  			}
   387  		}
   388  	}
   389  
   390  	return nil
   391  }
   392  
   393  func resourceCloudStackFirewallDeleteRule(
   394  	d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   395  	cs := meta.(*cloudstack.CloudStackClient)
   396  	uuids := rule["uuids"].(map[string]interface{})
   397  
   398  	for k, id := range uuids {
   399  		// We don't care about the count here, so just continue
   400  		if k == "#" {
   401  			continue
   402  		}
   403  
   404  		// Create the parameter struct
   405  		p := cs.Firewall.NewDeleteFirewallRuleParams(id.(string))
   406  
   407  		// Delete the rule
   408  		if _, err := cs.Firewall.DeleteFirewallRule(p); err != nil {
   409  
   410  			// This is a very poor way to be told the ID does no longer exist :(
   411  			if strings.Contains(err.Error(), fmt.Sprintf(
   412  				"Invalid parameter id value=%s due to incorrect long value format, "+
   413  					"or entity does not exist", id.(string))) {
   414  				delete(uuids, k)
   415  				continue
   416  			}
   417  
   418  			return err
   419  		}
   420  
   421  		// Delete the UUID of this rule
   422  		delete(uuids, k)
   423  	}
   424  
   425  	// Update the UUIDs
   426  	rule["uuids"] = uuids
   427  
   428  	return nil
   429  }
   430  
   431  func resourceCloudStackFirewallRuleHash(v interface{}) int {
   432  	var buf bytes.Buffer
   433  	m := v.(map[string]interface{})
   434  	buf.WriteString(fmt.Sprintf(
   435  		"%s-%s-", m["source_cidr"].(string), m["protocol"].(string)))
   436  
   437  	if v, ok := m["icmp_type"]; ok {
   438  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   439  	}
   440  
   441  	if v, ok := m["icmp_code"]; ok {
   442  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   443  	}
   444  
   445  	// We need to make sure to sort the strings below so that we always
   446  	// generate the same hash code no matter what is in the set.
   447  	if v, ok := m["ports"]; ok {
   448  		vs := v.(*schema.Set).List()
   449  		s := make([]string, len(vs))
   450  
   451  		for i, raw := range vs {
   452  			s[i] = raw.(string)
   453  		}
   454  		sort.Strings(s)
   455  
   456  		for _, v := range s {
   457  			buf.WriteString(fmt.Sprintf("%s-", v))
   458  		}
   459  	}
   460  
   461  	return hashcode.String(buf.String())
   462  }
   463  
   464  func verifyFirewallParams(d *schema.ResourceData) error {
   465  	managed := d.Get("managed").(bool)
   466  	_, rules := d.GetOk("rule")
   467  
   468  	if !rules && !managed {
   469  		return fmt.Errorf(
   470  			"You must supply at least one 'rule' when not using the 'managed' firewall feature")
   471  	}
   472  
   473  	return nil
   474  }
   475  
   476  func verifyFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   477  	protocol := rule["protocol"].(string)
   478  	if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
   479  		return fmt.Errorf(
   480  			"%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
   481  	}
   482  
   483  	if protocol == "icmp" {
   484  		if _, ok := rule["icmp_type"]; !ok {
   485  			return fmt.Errorf(
   486  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   487  		}
   488  		if _, ok := rule["icmp_code"]; !ok {
   489  			return fmt.Errorf(
   490  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   491  		}
   492  	} else {
   493  		if _, ok := rule["ports"]; !ok {
   494  			return fmt.Errorf(
   495  				"Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
   496  		}
   497  	}
   498  
   499  	return nil
   500  }