github.com/sathiyas/terraform@v0.6.9-0.20151210233947-3330da00b997/builtin/providers/cloudstack/resource_cloudstack_firewall.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/hashicorp/go-multierror"
    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  						"cidr_list": &schema.Schema{
    42  							Type:     schema.TypeSet,
    43  							Optional: true,
    44  							Elem:     &schema.Schema{Type: schema.TypeString},
    45  							Set:      schema.HashString,
    46  						},
    47  
    48  						"source_cidr": &schema.Schema{
    49  							Type:       schema.TypeString,
    50  							Optional:   true,
    51  							Deprecated: "Please use the `cidr_list` field instead",
    52  						},
    53  
    54  						"protocol": &schema.Schema{
    55  							Type:     schema.TypeString,
    56  							Required: true,
    57  						},
    58  
    59  						"icmp_type": &schema.Schema{
    60  							Type:     schema.TypeInt,
    61  							Optional: true,
    62  							Computed: true,
    63  						},
    64  
    65  						"icmp_code": &schema.Schema{
    66  							Type:     schema.TypeInt,
    67  							Optional: true,
    68  							Computed: true,
    69  						},
    70  
    71  						"ports": &schema.Schema{
    72  							Type:     schema.TypeSet,
    73  							Optional: true,
    74  							Elem:     &schema.Schema{Type: schema.TypeString},
    75  							Set:      schema.HashString,
    76  						},
    77  
    78  						"uuids": &schema.Schema{
    79  							Type:     schema.TypeMap,
    80  							Computed: true,
    81  						},
    82  					},
    83  				},
    84  			},
    85  		},
    86  	}
    87  }
    88  
    89  func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{}) error {
    90  	cs := meta.(*cloudstack.CloudStackClient)
    91  
    92  	// Make sure all required parameters are there
    93  	if err := verifyFirewallParams(d); err != nil {
    94  		return err
    95  	}
    96  
    97  	// Retrieve the ipaddress ID
    98  	ipaddressid, e := retrieveID(cs, "ipaddress", d.Get("ipaddress").(string))
    99  	if e != nil {
   100  		return e.Error()
   101  	}
   102  
   103  	// We need to set this upfront in order to be able to save a partial state
   104  	d.SetId(ipaddressid)
   105  
   106  	// Create all rules that are configured
   107  	if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 {
   108  		// Create an empty schema.Set to hold all rules
   109  		rules := resourceCloudStackFirewall().Schema["rule"].ZeroValue().(*schema.Set)
   110  
   111  		err := createFirewallRules(d, meta, rules, nrs)
   112  
   113  		// We need to update this first to preserve the correct state
   114  		d.Set("rule", rules)
   115  
   116  		if err != nil {
   117  			return err
   118  		}
   119  	}
   120  
   121  	return resourceCloudStackFirewallRead(d, meta)
   122  }
   123  func createFirewallRules(
   124  	d *schema.ResourceData,
   125  	meta interface{},
   126  	rules *schema.Set,
   127  	nrs *schema.Set) error {
   128  	var errs *multierror.Error
   129  
   130  	var wg sync.WaitGroup
   131  	wg.Add(nrs.Len())
   132  
   133  	sem := make(chan struct{}, 10)
   134  	for _, rule := range nrs.List() {
   135  		// Put in a tiny sleep here to avoid DoS'ing the API
   136  		time.Sleep(500 * time.Millisecond)
   137  
   138  		go func(rule map[string]interface{}) {
   139  			defer wg.Done()
   140  			sem <- struct{}{}
   141  
   142  			// Create a single rule
   143  			err := createFirewallRule(d, meta, rule)
   144  
   145  			// If we have at least one UUID, we need to save the rule
   146  			if len(rule["uuids"].(map[string]interface{})) > 0 {
   147  				rules.Add(rule)
   148  			}
   149  
   150  			if err != nil {
   151  				errs = multierror.Append(errs, err)
   152  			}
   153  
   154  			<-sem
   155  		}(rule.(map[string]interface{}))
   156  	}
   157  
   158  	wg.Wait()
   159  
   160  	return errs.ErrorOrNil()
   161  }
   162  
   163  func createFirewallRule(
   164  	d *schema.ResourceData,
   165  	meta interface{},
   166  	rule map[string]interface{}) error {
   167  	cs := meta.(*cloudstack.CloudStackClient)
   168  	uuids := rule["uuids"].(map[string]interface{})
   169  
   170  	// Make sure all required rule parameters are there
   171  	if err := verifyFirewallRuleParams(d, rule); err != nil {
   172  		return err
   173  	}
   174  
   175  	// Create a new parameter struct
   176  	p := cs.Firewall.NewCreateFirewallRuleParams(d.Id(), rule["protocol"].(string))
   177  
   178  	// Set the CIDR list
   179  	p.SetCidrlist(retrieveCidrList(rule))
   180  
   181  	// If the protocol is ICMP set the needed ICMP parameters
   182  	if rule["protocol"].(string) == "icmp" {
   183  		p.SetIcmptype(rule["icmp_type"].(int))
   184  		p.SetIcmpcode(rule["icmp_code"].(int))
   185  
   186  		r, err := cs.Firewall.CreateFirewallRule(p)
   187  		if err != nil {
   188  			return err
   189  		}
   190  
   191  		uuids["icmp"] = r.Id
   192  		rule["uuids"] = uuids
   193  	}
   194  
   195  	// If protocol is not ICMP, loop through all ports
   196  	if rule["protocol"].(string) != "icmp" {
   197  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   198  
   199  			// Create an empty schema.Set to hold all processed ports
   200  			ports := &schema.Set{F: schema.HashString}
   201  
   202  			// Define a regexp for parsing the port
   203  			re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
   204  
   205  			for _, port := range ps.List() {
   206  				if _, ok := uuids[port.(string)]; ok {
   207  					ports.Add(port)
   208  					rule["ports"] = ports
   209  					continue
   210  				}
   211  
   212  				m := re.FindStringSubmatch(port.(string))
   213  
   214  				startPort, err := strconv.Atoi(m[1])
   215  				if err != nil {
   216  					return err
   217  				}
   218  
   219  				endPort := startPort
   220  				if m[2] != "" {
   221  					endPort, err = strconv.Atoi(m[2])
   222  					if err != nil {
   223  						return err
   224  					}
   225  				}
   226  
   227  				p.SetStartport(startPort)
   228  				p.SetEndport(endPort)
   229  
   230  				r, err := cs.Firewall.CreateFirewallRule(p)
   231  				if err != nil {
   232  					return err
   233  				}
   234  
   235  				ports.Add(port)
   236  				rule["ports"] = ports
   237  
   238  				uuids[port.(string)] = r.Id
   239  				rule["uuids"] = uuids
   240  			}
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func resourceCloudStackFirewallRead(d *schema.ResourceData, meta interface{}) error {
   248  	cs := meta.(*cloudstack.CloudStackClient)
   249  
   250  	// Get all the rules from the running environment
   251  	p := cs.Firewall.NewListFirewallRulesParams()
   252  	p.SetIpaddressid(d.Id())
   253  	p.SetListall(true)
   254  
   255  	l, err := cs.Firewall.ListFirewallRules(p)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	// Make a map of all the rules so we can easily find a rule
   261  	ruleMap := make(map[string]*cloudstack.FirewallRule, l.Count)
   262  	for _, r := range l.FirewallRules {
   263  		ruleMap[r.Id] = r
   264  	}
   265  
   266  	// Create an empty schema.Set to hold all rules
   267  	rules := resourceCloudStackFirewall().Schema["rule"].ZeroValue().(*schema.Set)
   268  
   269  	// Read all rules that are configured
   270  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   271  		for _, rule := range rs.List() {
   272  			rule := rule.(map[string]interface{})
   273  			uuids := rule["uuids"].(map[string]interface{})
   274  
   275  			if rule["protocol"].(string) == "icmp" {
   276  				id, ok := uuids["icmp"]
   277  				if !ok {
   278  					continue
   279  				}
   280  
   281  				// Get the rule
   282  				r, ok := ruleMap[id.(string)]
   283  				if !ok {
   284  					delete(uuids, "icmp")
   285  					continue
   286  				}
   287  
   288  				// Delete the known rule so only unknown rules remain in the ruleMap
   289  				delete(ruleMap, id.(string))
   290  
   291  				// Update the values
   292  				rule["protocol"] = r.Protocol
   293  				rule["icmp_type"] = r.Icmptype
   294  				rule["icmp_code"] = r.Icmpcode
   295  				setCidrList(rule, r.Cidrlist)
   296  				rules.Add(rule)
   297  			}
   298  
   299  			// If protocol is not ICMP, loop through all ports
   300  			if rule["protocol"].(string) != "icmp" {
   301  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   302  
   303  					// Create an empty schema.Set to hold all ports
   304  					ports := &schema.Set{F: schema.HashString}
   305  
   306  					// Loop through all ports and retrieve their info
   307  					for _, port := range ps.List() {
   308  						id, ok := uuids[port.(string)]
   309  						if !ok {
   310  							continue
   311  						}
   312  
   313  						// Get the rule
   314  						r, ok := ruleMap[id.(string)]
   315  						if !ok {
   316  							delete(uuids, port.(string))
   317  							continue
   318  						}
   319  
   320  						// Delete the known rule so only unknown rules remain in the ruleMap
   321  						delete(ruleMap, id.(string))
   322  
   323  						// Update the values
   324  						rule["protocol"] = r.Protocol
   325  						setCidrList(rule, r.Cidrlist)
   326  						ports.Add(port)
   327  					}
   328  
   329  					// If there is at least one port found, add this rule to the rules set
   330  					if ports.Len() > 0 {
   331  						rule["ports"] = ports
   332  						rules.Add(rule)
   333  					}
   334  				}
   335  			}
   336  		}
   337  	}
   338  
   339  	// If this is a managed firewall, add all unknown rules into a single dummy rule
   340  	managed := d.Get("managed").(bool)
   341  	if managed && len(ruleMap) > 0 {
   342  		for uuid := range ruleMap {
   343  			// We need to create and add a dummy value to a schema.Set as the
   344  			// cidr_list is a required field and thus needs a value
   345  			cidrs := &schema.Set{F: schema.HashString}
   346  			cidrs.Add(uuid)
   347  
   348  			// Make a dummy rule to hold the unknown UUID
   349  			rule := map[string]interface{}{
   350  				"cidr_list": cidrs,
   351  				"protocol":  uuid,
   352  				"uuids":     map[string]interface{}{uuid: uuid},
   353  			}
   354  
   355  			// Add the dummy rule to the rules set
   356  			rules.Add(rule)
   357  		}
   358  	}
   359  
   360  	if rules.Len() > 0 {
   361  		d.Set("rule", rules)
   362  	} else if !managed {
   363  		d.SetId("")
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  func resourceCloudStackFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
   370  	// Make sure all required parameters are there
   371  	if err := verifyFirewallParams(d); err != nil {
   372  		return err
   373  	}
   374  
   375  	// Check if the rule set as a whole has changed
   376  	if d.HasChange("rule") {
   377  		o, n := d.GetChange("rule")
   378  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   379  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   380  
   381  		// We need to start with a rule set containing all the rules we
   382  		// already have and want to keep. Any rules that are not deleted
   383  		// correctly and any newly created rules, will be added to this
   384  		// set to make sure we end up in a consistent state
   385  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   386  
   387  		// First loop through all the old rules and delete them
   388  		if ors.Len() > 0 {
   389  			err := deleteFirewallRules(d, meta, rules, ors)
   390  
   391  			// We need to update this first to preserve the correct state
   392  			d.Set("rule", rules)
   393  
   394  			if err != nil {
   395  				return err
   396  			}
   397  		}
   398  
   399  		// Then loop through all the new rules and create them
   400  		if nrs.Len() > 0 {
   401  			err := createFirewallRules(d, meta, rules, nrs)
   402  
   403  			// We need to update this first to preserve the correct state
   404  			d.Set("rule", rules)
   405  
   406  			if err != nil {
   407  				return err
   408  			}
   409  		}
   410  	}
   411  
   412  	return resourceCloudStackFirewallRead(d, meta)
   413  }
   414  
   415  func resourceCloudStackFirewallDelete(d *schema.ResourceData, meta interface{}) error {
   416  	// Create an empty rule set to hold all rules that where
   417  	// not deleted correctly
   418  	rules := resourceCloudStackFirewall().Schema["rule"].ZeroValue().(*schema.Set)
   419  
   420  	// Delete all rules
   421  	if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 {
   422  		err := deleteFirewallRules(d, meta, rules, ors)
   423  
   424  		// We need to update this first to preserve the correct state
   425  		d.Set("rule", rules)
   426  
   427  		if err != nil {
   428  			return err
   429  		}
   430  	}
   431  
   432  	return nil
   433  }
   434  
   435  func deleteFirewallRules(
   436  	d *schema.ResourceData,
   437  	meta interface{},
   438  	rules *schema.Set,
   439  	ors *schema.Set) error {
   440  	var errs *multierror.Error
   441  
   442  	var wg sync.WaitGroup
   443  	wg.Add(ors.Len())
   444  
   445  	sem := make(chan struct{}, 10)
   446  	for _, rule := range ors.List() {
   447  		// Put a sleep here to avoid DoS'ing the API
   448  		time.Sleep(500 * time.Millisecond)
   449  
   450  		go func(rule map[string]interface{}) {
   451  			defer wg.Done()
   452  			sem <- struct{}{}
   453  
   454  			// Delete a single rule
   455  			err := deleteFirewallRule(d, meta, rule)
   456  
   457  			// If we have at least one UUID, we need to save the rule
   458  			if len(rule["uuids"].(map[string]interface{})) > 0 {
   459  				rules.Add(rule)
   460  			}
   461  
   462  			if err != nil {
   463  				errs = multierror.Append(errs, err)
   464  			}
   465  
   466  			<-sem
   467  		}(rule.(map[string]interface{}))
   468  	}
   469  
   470  	wg.Wait()
   471  
   472  	return errs.ErrorOrNil()
   473  }
   474  
   475  func deleteFirewallRule(
   476  	d *schema.ResourceData,
   477  	meta interface{},
   478  	rule map[string]interface{}) error {
   479  	cs := meta.(*cloudstack.CloudStackClient)
   480  	uuids := rule["uuids"].(map[string]interface{})
   481  
   482  	for k, id := range uuids {
   483  		// We don't care about the count here, so just continue
   484  		if k == "#" {
   485  			continue
   486  		}
   487  
   488  		// Create the parameter struct
   489  		p := cs.Firewall.NewDeleteFirewallRuleParams(id.(string))
   490  
   491  		// Delete the rule
   492  		if _, err := cs.Firewall.DeleteFirewallRule(p); err != nil {
   493  
   494  			// This is a very poor way to be told the ID does no longer exist :(
   495  			if strings.Contains(err.Error(), fmt.Sprintf(
   496  				"Invalid parameter id value=%s due to incorrect long value format, "+
   497  					"or entity does not exist", id.(string))) {
   498  				delete(uuids, k)
   499  				continue
   500  			}
   501  
   502  			return err
   503  		}
   504  
   505  		// Delete the UUID of this rule
   506  		delete(uuids, k)
   507  		rule["uuids"] = uuids
   508  	}
   509  
   510  	return nil
   511  }
   512  
   513  func verifyFirewallParams(d *schema.ResourceData) error {
   514  	managed := d.Get("managed").(bool)
   515  	_, rules := d.GetOk("rule")
   516  
   517  	if !rules && !managed {
   518  		return fmt.Errorf(
   519  			"You must supply at least one 'rule' when not using the 'managed' firewall feature")
   520  	}
   521  
   522  	return nil
   523  }
   524  
   525  func verifyFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   526  	cidrList := rule["cidr_list"].(*schema.Set)
   527  	sourceCidr := rule["source_cidr"].(string)
   528  	if cidrList.Len() == 0 && sourceCidr == "" {
   529  		return fmt.Errorf(
   530  			"Parameter cidr_list is a required parameter")
   531  	}
   532  	if cidrList.Len() > 0 && sourceCidr != "" {
   533  		return fmt.Errorf(
   534  			"Parameter source_cidr is deprecated and cannot be used together with cidr_list")
   535  	}
   536  
   537  	protocol := rule["protocol"].(string)
   538  	if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
   539  		return fmt.Errorf(
   540  			"%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
   541  	}
   542  
   543  	if protocol == "icmp" {
   544  		if _, ok := rule["icmp_type"]; !ok {
   545  			return fmt.Errorf(
   546  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   547  		}
   548  		if _, ok := rule["icmp_code"]; !ok {
   549  			return fmt.Errorf(
   550  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   551  		}
   552  	} else {
   553  		if _, ok := rule["ports"]; !ok {
   554  			return fmt.Errorf(
   555  				"Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
   556  		}
   557  	}
   558  
   559  	return nil
   560  }