github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go (about)

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