github.com/sathiyas/terraform@v0.6.9-0.20151210233947-3330da00b997/builtin/providers/cloudstack/resource_cloudstack_egress_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 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  						"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 resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error {
    90  	cs := meta.(*cloudstack.CloudStackClient)
    91  
    92  	// Make sure all required parameters are there
    93  	if err := verifyEgressFirewallParams(d); err != nil {
    94  		return err
    95  	}
    96  
    97  	// Retrieve the network ID
    98  	networkid, e := retrieveID(cs, "network", d.Get("network").(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(networkid)
   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 := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set)
   110  
   111  		err := createEgressFirewallRules(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 resourceCloudStackEgressFirewallRead(d, meta)
   122  }
   123  
   124  func createEgressFirewallRules(
   125  	d *schema.ResourceData,
   126  	meta interface{},
   127  	rules *schema.Set,
   128  	nrs *schema.Set) error {
   129  	var errs *multierror.Error
   130  
   131  	var wg sync.WaitGroup
   132  	wg.Add(nrs.Len())
   133  
   134  	sem := make(chan struct{}, 10)
   135  	for _, rule := range nrs.List() {
   136  		// Put in a tiny sleep here to avoid DoS'ing the API
   137  		time.Sleep(500 * time.Millisecond)
   138  
   139  		go func(rule map[string]interface{}) {
   140  			defer wg.Done()
   141  			sem <- struct{}{}
   142  
   143  			// Create a single rule
   144  			err := createEgressFirewallRule(d, meta, rule)
   145  
   146  			// If we have at least one UUID, we need to save the rule
   147  			if len(rule["uuids"].(map[string]interface{})) > 0 {
   148  				rules.Add(rule)
   149  			}
   150  
   151  			if err != nil {
   152  				errs = multierror.Append(errs, err)
   153  			}
   154  
   155  			<-sem
   156  		}(rule.(map[string]interface{}))
   157  	}
   158  
   159  	wg.Wait()
   160  
   161  	return errs.ErrorOrNil()
   162  }
   163  func createEgressFirewallRule(
   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 := verifyEgressFirewallRuleParams(d, rule); err != nil {
   172  		return err
   173  	}
   174  
   175  	// Create a new parameter struct
   176  	p := cs.Firewall.NewCreateEgressFirewallRuleParams(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.CreateEgressFirewallRule(p)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		uuids["icmp"] = r.Id
   191  		rule["uuids"] = uuids
   192  	}
   193  
   194  	// If protocol is not ICMP, loop through all ports
   195  	if rule["protocol"].(string) != "icmp" {
   196  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   197  
   198  			// Create an empty schema.Set to hold all processed ports
   199  			ports := &schema.Set{F: schema.HashString}
   200  
   201  			// Define a regexp for parsing the port
   202  			re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
   203  
   204  			for _, port := range ps.List() {
   205  				if _, ok := uuids[port.(string)]; ok {
   206  					ports.Add(port)
   207  					rule["ports"] = ports
   208  					continue
   209  				}
   210  
   211  				m := re.FindStringSubmatch(port.(string))
   212  
   213  				startPort, err := strconv.Atoi(m[1])
   214  				if err != nil {
   215  					return err
   216  				}
   217  
   218  				endPort := startPort
   219  				if m[2] != "" {
   220  					endPort, err = strconv.Atoi(m[2])
   221  					if err != nil {
   222  						return err
   223  					}
   224  				}
   225  
   226  				p.SetStartport(startPort)
   227  				p.SetEndport(endPort)
   228  
   229  				r, err := cs.Firewall.CreateEgressFirewallRule(p)
   230  				if err != nil {
   231  					return err
   232  				}
   233  
   234  				ports.Add(port)
   235  				rule["ports"] = ports
   236  
   237  				uuids[port.(string)] = r.Id
   238  				rule["uuids"] = uuids
   239  			}
   240  		}
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error {
   247  	cs := meta.(*cloudstack.CloudStackClient)
   248  
   249  	// Get all the rules from the running environment
   250  	p := cs.Firewall.NewListEgressFirewallRulesParams()
   251  	p.SetNetworkid(d.Id())
   252  	p.SetListall(true)
   253  
   254  	l, err := cs.Firewall.ListEgressFirewallRules(p)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	// Make a map of all the rules so we can easily find a rule
   260  	ruleMap := make(map[string]*cloudstack.EgressFirewallRule, l.Count)
   261  	for _, r := range l.EgressFirewallRules {
   262  		ruleMap[r.Id] = r
   263  	}
   264  
   265  	// Create an empty schema.Set to hold all rules
   266  	rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set)
   267  
   268  	// Read all rules that are configured
   269  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   270  		for _, rule := range rs.List() {
   271  			rule := rule.(map[string]interface{})
   272  			uuids := rule["uuids"].(map[string]interface{})
   273  
   274  			if rule["protocol"].(string) == "icmp" {
   275  				id, ok := uuids["icmp"]
   276  				if !ok {
   277  					continue
   278  				}
   279  
   280  				// Get the rule
   281  				r, ok := ruleMap[id.(string)]
   282  				if !ok {
   283  					delete(uuids, "icmp")
   284  					continue
   285  				}
   286  
   287  				// Delete the known rule so only unknown rules remain in the ruleMap
   288  				delete(ruleMap, id.(string))
   289  
   290  				// Update the values
   291  				rule["protocol"] = r.Protocol
   292  				rule["icmp_type"] = r.Icmptype
   293  				rule["icmp_code"] = r.Icmpcode
   294  				setCidrList(rule, r.Cidrlist)
   295  				rules.Add(rule)
   296  			}
   297  
   298  			// If protocol is not ICMP, loop through all ports
   299  			if rule["protocol"].(string) != "icmp" {
   300  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   301  
   302  					// Create an empty schema.Set to hold all ports
   303  					ports := &schema.Set{F: schema.HashString}
   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, ok := ruleMap[id.(string)]
   314  						if !ok {
   315  							delete(uuids, port.(string))
   316  							continue
   317  						}
   318  
   319  						// Delete the known rule so only unknown rules remain in the ruleMap
   320  						delete(ruleMap, id.(string))
   321  
   322  						// Update the values
   323  						rule["protocol"] = r.Protocol
   324  						setCidrList(rule, r.Cidrlist)
   325  						ports.Add(port)
   326  					}
   327  
   328  					// If there is at least one port found, add this rule to the rules set
   329  					if ports.Len() > 0 {
   330  						rule["ports"] = ports
   331  						rules.Add(rule)
   332  					}
   333  				}
   334  			}
   335  		}
   336  	}
   337  
   338  	// If this is a managed firewall, add all unknown rules into a single dummy rule
   339  	managed := d.Get("managed").(bool)
   340  	if managed && len(ruleMap) > 0 {
   341  		for uuid := range ruleMap {
   342  			// We need to create and add a dummy value to a schema.Set as the
   343  			// cidr_list is a required field and thus needs a value
   344  			cidrs := &schema.Set{F: schema.HashString}
   345  			cidrs.Add(uuid)
   346  
   347  			// Make a dummy rule to hold the unknown UUID
   348  			rule := map[string]interface{}{
   349  				"cidr_list": uuid,
   350  				"protocol":  uuid,
   351  				"uuids":     map[string]interface{}{uuid: uuid},
   352  			}
   353  
   354  			// Add the dummy rule to the rules set
   355  			rules.Add(rule)
   356  		}
   357  	}
   358  
   359  	if rules.Len() > 0 {
   360  		d.Set("rule", rules)
   361  	} else if !managed {
   362  		d.SetId("")
   363  	}
   364  
   365  	return nil
   366  }
   367  
   368  func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
   369  	// Make sure all required parameters are there
   370  	if err := verifyEgressFirewallParams(d); err != nil {
   371  		return err
   372  	}
   373  
   374  	// Check if the rule set as a whole has changed
   375  	if d.HasChange("rule") {
   376  		o, n := d.GetChange("rule")
   377  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   378  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   379  
   380  		// We need to start with a rule set containing all the rules we
   381  		// already have and want to keep. Any rules that are not deleted
   382  		// correctly and any newly created rules, will be added to this
   383  		// set to make sure we end up in a consistent state
   384  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   385  
   386  		// First loop through all the old rules and delete them
   387  		if ors.Len() > 0 {
   388  			err := deleteEgressFirewallRules(d, meta, rules, ors)
   389  
   390  			// We need to update this first to preserve the correct state
   391  			d.Set("rule", rules)
   392  
   393  			if err != nil {
   394  				return err
   395  			}
   396  		}
   397  
   398  		// Then loop through all the new rules and create them
   399  		if nrs.Len() > 0 {
   400  			err := createEgressFirewallRules(d, meta, rules, nrs)
   401  
   402  			// We need to update this first to preserve the correct state
   403  			d.Set("rule", rules)
   404  
   405  			if err != nil {
   406  				return err
   407  			}
   408  		}
   409  	}
   410  
   411  	return resourceCloudStackEgressFirewallRead(d, meta)
   412  }
   413  
   414  func resourceCloudStackEgressFirewallDelete(d *schema.ResourceData, meta interface{}) error {
   415  	// Create an empty rule set to hold all rules that where
   416  	// not deleted correctly
   417  	rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set)
   418  
   419  	// Delete all rules
   420  	if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 {
   421  		err := deleteEgressFirewallRules(d, meta, rules, ors)
   422  
   423  		// We need to update this first to preserve the correct state
   424  		d.Set("rule", rules)
   425  
   426  		if err != nil {
   427  			return err
   428  		}
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  func deleteEgressFirewallRules(
   435  	d *schema.ResourceData,
   436  	meta interface{},
   437  	rules *schema.Set,
   438  	ors *schema.Set) error {
   439  	var errs *multierror.Error
   440  
   441  	var wg sync.WaitGroup
   442  	wg.Add(ors.Len())
   443  
   444  	sem := make(chan struct{}, 10)
   445  	for _, rule := range ors.List() {
   446  		// Put a sleep here to avoid DoS'ing the API
   447  		time.Sleep(500 * time.Millisecond)
   448  
   449  		go func(rule map[string]interface{}) {
   450  			defer wg.Done()
   451  			sem <- struct{}{}
   452  
   453  			// Delete a single rule
   454  			err := deleteEgressFirewallRule(d, meta, rule)
   455  
   456  			// If we have at least one UUID, we need to save the rule
   457  			if len(rule["uuids"].(map[string]interface{})) > 0 {
   458  				rules.Add(rule)
   459  			}
   460  
   461  			if err != nil {
   462  				errs = multierror.Append(errs, err)
   463  			}
   464  
   465  			<-sem
   466  		}(rule.(map[string]interface{}))
   467  	}
   468  
   469  	wg.Wait()
   470  
   471  	return errs.ErrorOrNil()
   472  }
   473  
   474  func deleteEgressFirewallRule(
   475  	d *schema.ResourceData,
   476  	meta interface{},
   477  	rule map[string]interface{}) error {
   478  	cs := meta.(*cloudstack.CloudStackClient)
   479  	uuids := rule["uuids"].(map[string]interface{})
   480  
   481  	for k, id := range uuids {
   482  		// We don't care about the count here, so just continue
   483  		if k == "#" {
   484  			continue
   485  		}
   486  
   487  		// Create the parameter struct
   488  		p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string))
   489  
   490  		// Delete the rule
   491  		if _, err := cs.Firewall.DeleteEgressFirewallRule(p); err != nil {
   492  
   493  			// This is a very poor way to be told the ID does no longer exist :(
   494  			if strings.Contains(err.Error(), fmt.Sprintf(
   495  				"Invalid parameter id value=%s due to incorrect long value format, "+
   496  					"or entity does not exist", id.(string))) {
   497  				delete(uuids, k)
   498  				continue
   499  			}
   500  
   501  			return err
   502  		}
   503  
   504  		// Delete the UUID of this rule
   505  		delete(uuids, k)
   506  		rule["uuids"] = uuids
   507  	}
   508  
   509  	return nil
   510  }
   511  
   512  func verifyEgressFirewallParams(d *schema.ResourceData) error {
   513  	managed := d.Get("managed").(bool)
   514  	_, rules := d.GetOk("rule")
   515  
   516  	if !rules && !managed {
   517  		return fmt.Errorf(
   518  			"You must supply at least one 'rule' when not using the 'managed' firewall feature")
   519  	}
   520  
   521  	return nil
   522  }
   523  
   524  func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   525  	cidrList := rule["cidr_list"].(*schema.Set)
   526  	sourceCidr := rule["source_cidr"].(string)
   527  	if cidrList.Len() == 0 && sourceCidr == "" {
   528  		return fmt.Errorf(
   529  			"Parameter cidr_list is a required parameter")
   530  	}
   531  	if cidrList.Len() > 0 && sourceCidr != "" {
   532  		return fmt.Errorf(
   533  			"Parameter source_cidr is deprecated and cannot be used together with cidr_list")
   534  	}
   535  
   536  	protocol := rule["protocol"].(string)
   537  	if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
   538  		return fmt.Errorf(
   539  			"%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
   540  	}
   541  
   542  	if protocol == "icmp" {
   543  		if _, ok := rule["icmp_type"]; !ok {
   544  			return fmt.Errorf(
   545  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   546  		}
   547  		if _, ok := rule["icmp_code"]; !ok {
   548  			return fmt.Errorf(
   549  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   550  		}
   551  	} else {
   552  		if _, ok := rule["ports"]; !ok {
   553  			return fmt.Errorf(
   554  				"Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
   555  		}
   556  	}
   557  
   558  	return nil
   559  }