github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/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  			"rule": &schema.Schema{
    31  				Type:     schema.TypeSet,
    32  				Required: true,
    33  				Elem: &schema.Resource{
    34  					Schema: map[string]*schema.Schema{
    35  						"source_cidr": &schema.Schema{
    36  							Type:     schema.TypeString,
    37  							Required: true,
    38  						},
    39  
    40  						"protocol": &schema.Schema{
    41  							Type:     schema.TypeString,
    42  							Required: true,
    43  						},
    44  
    45  						"icmp_type": &schema.Schema{
    46  							Type:     schema.TypeInt,
    47  							Optional: true,
    48  							Computed: true,
    49  						},
    50  
    51  						"icmp_code": &schema.Schema{
    52  							Type:     schema.TypeInt,
    53  							Optional: true,
    54  							Computed: true,
    55  						},
    56  
    57  						"ports": &schema.Schema{
    58  							Type:     schema.TypeSet,
    59  							Optional: true,
    60  							Elem:     &schema.Schema{Type: schema.TypeString},
    61  							Set: func(v interface{}) int {
    62  								return hashcode.String(v.(string))
    63  							},
    64  						},
    65  
    66  						"uuids": &schema.Schema{
    67  							Type:     schema.TypeMap,
    68  							Computed: true,
    69  						},
    70  					},
    71  				},
    72  				Set: resourceCloudStackFirewallRuleHash,
    73  			},
    74  		},
    75  	}
    76  }
    77  
    78  func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{}) error {
    79  	cs := meta.(*cloudstack.CloudStackClient)
    80  
    81  	// Retrieve the ipaddress UUID
    82  	ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
    83  	if e != nil {
    84  		return e.Error()
    85  	}
    86  
    87  	// We need to set this upfront in order to be able to save a partial state
    88  	d.SetId(d.Get("ipaddress").(string))
    89  
    90  	// Create all rules that are configured
    91  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
    92  
    93  		// Create an empty schema.Set to hold all rules
    94  		rules := &schema.Set{
    95  			F: resourceCloudStackFirewallRuleHash,
    96  		}
    97  
    98  		for _, rule := range rs.List() {
    99  			// Create a single rule
   100  			err := resourceCloudStackFirewallCreateRule(d, meta, ipaddressid, rule.(map[string]interface{}))
   101  
   102  			// We need to update this first to preserve the correct state
   103  			rules.Add(rule)
   104  			d.Set("rule", rules)
   105  
   106  			if err != nil {
   107  				return err
   108  			}
   109  		}
   110  	}
   111  
   112  	return resourceCloudStackFirewallRead(d, meta)
   113  }
   114  
   115  func resourceCloudStackFirewallCreateRule(
   116  	d *schema.ResourceData, meta interface{}, ipaddressid string, rule map[string]interface{}) error {
   117  	cs := meta.(*cloudstack.CloudStackClient)
   118  	uuids := rule["uuids"].(map[string]interface{})
   119  
   120  	// Make sure all required parameters are there
   121  	if err := verifyFirewallParams(d, rule); err != nil {
   122  		return err
   123  	}
   124  
   125  	// Create a new parameter struct
   126  	p := cs.Firewall.NewCreateFirewallRuleParams(ipaddressid, rule["protocol"].(string))
   127  
   128  	// Set the CIDR list
   129  	p.SetCidrlist([]string{rule["source_cidr"].(string)})
   130  
   131  	// If the protocol is ICMP set the needed ICMP parameters
   132  	if rule["protocol"].(string) == "icmp" {
   133  		p.SetIcmptype(rule["icmp_type"].(int))
   134  		p.SetIcmpcode(rule["icmp_code"].(int))
   135  
   136  		r, err := cs.Firewall.CreateFirewallRule(p)
   137  		if err != nil {
   138  			return err
   139  		}
   140  		uuids["icmp"] = r.Id
   141  		rule["uuids"] = uuids
   142  	}
   143  
   144  	// If protocol is not ICMP, loop through all ports
   145  	if rule["protocol"].(string) != "icmp" {
   146  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   147  
   148  			// Create an empty schema.Set to hold all processed ports
   149  			ports := &schema.Set{
   150  				F: func(v interface{}) int {
   151  					return hashcode.String(v.(string))
   152  				},
   153  			}
   154  
   155  			for _, port := range ps.List() {
   156  				re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
   157  				m := re.FindStringSubmatch(port.(string))
   158  
   159  				startPort, err := strconv.Atoi(m[1])
   160  				if err != nil {
   161  					return err
   162  				}
   163  
   164  				endPort := startPort
   165  				if m[2] != "" {
   166  					endPort, err = strconv.Atoi(m[2])
   167  					if err != nil {
   168  						return err
   169  					}
   170  				}
   171  
   172  				p.SetStartport(startPort)
   173  				p.SetEndport(endPort)
   174  
   175  				r, err := cs.Firewall.CreateFirewallRule(p)
   176  				if err != nil {
   177  					return err
   178  				}
   179  
   180  				ports.Add(port)
   181  				rule["ports"] = ports
   182  
   183  				uuids[port.(string)] = r.Id
   184  				rule["uuids"] = uuids
   185  			}
   186  		}
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) error {
   193  	cs := meta.(*cloudstack.CloudStackClient)
   194  
   195  	// Create an empty schema.Set to hold all rules
   196  	rules := &schema.Set{
   197  		F: resourceCloudStackFirewallRuleHash,
   198  	}
   199  
   200  	// Read all rules that are configured
   201  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   202  		for _, rule := range rs.List() {
   203  			rule := rule.(map[string]interface{})
   204  			uuids := rule["uuids"].(map[string]interface{})
   205  
   206  			if rule["protocol"].(string) == "icmp" {
   207  				id, ok := uuids["icmp"]
   208  				if !ok {
   209  					continue
   210  				}
   211  
   212  				// Get the rule
   213  				r, count, err := cs.Firewall.GetFirewallRuleByID(id.(string))
   214  				// If the count == 0, there is no object found for this UUID
   215  				if err != nil {
   216  					if count == 0 {
   217  						delete(uuids, "icmp")
   218  						continue
   219  					}
   220  
   221  					return err
   222  				}
   223  
   224  				// Update the values
   225  				rule["source_cidr"] = r.Cidrlist
   226  				rule["protocol"] = r.Protocol
   227  				rule["icmp_type"] = r.Icmptype
   228  				rule["icmp_code"] = r.Icmpcode
   229  				rules.Add(rule)
   230  			}
   231  
   232  			// If protocol is not ICMP, loop through all ports
   233  			if rule["protocol"].(string) != "icmp" {
   234  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   235  
   236  					// Create an empty schema.Set to hold all ports
   237  					ports := &schema.Set{
   238  						F: func(v interface{}) int {
   239  							return hashcode.String(v.(string))
   240  						},
   241  					}
   242  
   243  					// Loop through all ports and retrieve their info
   244  					for _, port := range ps.List() {
   245  						id, ok := uuids[port.(string)]
   246  						if !ok {
   247  							continue
   248  						}
   249  
   250  						// Get the rule
   251  						r, count, err := cs.Firewall.GetFirewallRuleByID(id.(string))
   252  						if err != nil {
   253  							if count == 0 {
   254  								delete(uuids, port.(string))
   255  								continue
   256  							}
   257  
   258  							return err
   259  						}
   260  
   261  						// Update the values
   262  						rule["source_cidr"] = r.Cidrlist
   263  						rule["protocol"] = r.Protocol
   264  						ports.Add(port)
   265  					}
   266  
   267  					// If there is at least one port found, add this rule to the rules set
   268  					if ports.Len() > 0 {
   269  						rule["ports"] = ports
   270  						rules.Add(rule)
   271  					}
   272  				}
   273  			}
   274  		}
   275  	}
   276  
   277  	if rules.Len() > 0 {
   278  		d.Set("rule", rules)
   279  	} else {
   280  		d.SetId("")
   281  	}
   282  
   283  	return nil
   284  }
   285  
   286  func resourceCloudStackFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
   287  	cs := meta.(*cloudstack.CloudStackClient)
   288  
   289  	// Retrieve the ipaddress UUID
   290  	ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
   291  	if e != nil {
   292  		return e.Error()
   293  	}
   294  
   295  	// Check if the rule set as a whole has changed
   296  	if d.HasChange("rule") {
   297  		o, n := d.GetChange("rule")
   298  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   299  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   300  
   301  		// Now first loop through all the old rules and delete any obsolete ones
   302  		for _, rule := range ors.List() {
   303  			// Delete the rule as it no longer exists in the config
   304  			err := resourceCloudStackFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
   305  			if err != nil {
   306  				return err
   307  			}
   308  		}
   309  
   310  		// Make sure we save the state of the currently configured rules
   311  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   312  		d.Set("rule", rules)
   313  
   314  		// Then loop through al the currently configured rules and create the new ones
   315  		for _, rule := range nrs.List() {
   316  			// When succesfully deleted, re-create it again if it still exists
   317  			err := resourceCloudStackFirewallCreateRule(
   318  				d, meta, ipaddressid, rule.(map[string]interface{}))
   319  
   320  			// We need to update this first to preserve the correct state
   321  			rules.Add(rule)
   322  			d.Set("rule", rules)
   323  
   324  			if err != nil {
   325  				return err
   326  			}
   327  		}
   328  	}
   329  
   330  	return resourceCloudStackFirewallRead(d, meta)
   331  }
   332  
   333  func resourceCloudStackFirewallDelete(d *schema.ResourceData, meta interface{}) error {
   334  	// Delete all rules
   335  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   336  		for _, rule := range rs.List() {
   337  			// Delete a single rule
   338  			err := resourceCloudStackFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
   339  
   340  			// We need to update this first to preserve the correct state
   341  			d.Set("rule", rs)
   342  
   343  			if err != nil {
   344  				return err
   345  			}
   346  		}
   347  	}
   348  
   349  	return nil
   350  }
   351  
   352  func resourceCloudStackFirewallDeleteRule(
   353  	d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   354  	cs := meta.(*cloudstack.CloudStackClient)
   355  	uuids := rule["uuids"].(map[string]interface{})
   356  
   357  	for k, id := range uuids {
   358  		// Create the parameter struct
   359  		p := cs.Firewall.NewDeleteFirewallRuleParams(id.(string))
   360  
   361  		// Delete the rule
   362  		if _, err := cs.Firewall.DeleteFirewallRule(p); err != nil {
   363  
   364  			// This is a very poor way to be told the UUID does no longer exist :(
   365  			if strings.Contains(err.Error(), fmt.Sprintf(
   366  				"Invalid parameter id value=%s due to incorrect long value format, "+
   367  					"or entity does not exist", id.(string))) {
   368  				delete(uuids, k)
   369  				continue
   370  			}
   371  
   372  			return err
   373  		}
   374  
   375  		// Delete the UUID of this rule
   376  		delete(uuids, k)
   377  	}
   378  
   379  	// Update the UUIDs
   380  	rule["uuids"] = uuids
   381  
   382  	return nil
   383  }
   384  
   385  func resourceCloudStackFirewallRuleHash(v interface{}) int {
   386  	var buf bytes.Buffer
   387  	m := v.(map[string]interface{})
   388  	buf.WriteString(fmt.Sprintf(
   389  		"%s-%s-", m["source_cidr"].(string), m["protocol"].(string)))
   390  
   391  	if v, ok := m["icmp_type"]; ok {
   392  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   393  	}
   394  
   395  	if v, ok := m["icmp_code"]; ok {
   396  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   397  	}
   398  
   399  	// We need to make sure to sort the strings below so that we always
   400  	// generate the same hash code no matter what is in the set.
   401  	if v, ok := m["ports"]; ok {
   402  		vs := v.(*schema.Set).List()
   403  		s := make([]string, len(vs))
   404  
   405  		for i, raw := range vs {
   406  			s[i] = raw.(string)
   407  		}
   408  		sort.Strings(s)
   409  
   410  		for _, v := range s {
   411  			buf.WriteString(fmt.Sprintf("%s-", v))
   412  		}
   413  	}
   414  
   415  	return hashcode.String(buf.String())
   416  }
   417  
   418  func verifyFirewallParams(d *schema.ResourceData, rule map[string]interface{}) error {
   419  	protocol := rule["protocol"].(string)
   420  	if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
   421  		return fmt.Errorf(
   422  			"%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
   423  	}
   424  
   425  	if protocol == "icmp" {
   426  		if _, ok := rule["icmp_type"]; !ok {
   427  			return fmt.Errorf(
   428  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   429  		}
   430  		if _, ok := rule["icmp_code"]; !ok {
   431  			return fmt.Errorf(
   432  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   433  		}
   434  	} else {
   435  		if _, ok := rule["ports"]; !ok {
   436  			return fmt.Errorf(
   437  				"Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
   438  		}
   439  	}
   440  
   441  	return nil
   442  }