github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.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 resourceCloudStackNetworkACLRule() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceCloudStackNetworkACLRuleCreate,
    18  		Read:   resourceCloudStackNetworkACLRuleRead,
    19  		Update: resourceCloudStackNetworkACLRuleUpdate,
    20  		Delete: resourceCloudStackNetworkACLRuleDelete,
    21  
    22  		Schema: map[string]*schema.Schema{
    23  			"acl_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  						"action": &schema.Schema{
    41  							Type:     schema.TypeString,
    42  							Optional: true,
    43  							Default:  "allow",
    44  						},
    45  
    46  						"cidr_list": &schema.Schema{
    47  							Type:     schema.TypeSet,
    48  							Required: true,
    49  							Elem:     &schema.Schema{Type: schema.TypeString},
    50  							Set:      schema.HashString,
    51  						},
    52  
    53  						"protocol": &schema.Schema{
    54  							Type:     schema.TypeString,
    55  							Required: true,
    56  						},
    57  
    58  						"icmp_type": &schema.Schema{
    59  							Type:     schema.TypeInt,
    60  							Optional: true,
    61  							Computed: true,
    62  						},
    63  
    64  						"icmp_code": &schema.Schema{
    65  							Type:     schema.TypeInt,
    66  							Optional: true,
    67  							Computed: true,
    68  						},
    69  
    70  						"ports": &schema.Schema{
    71  							Type:     schema.TypeSet,
    72  							Optional: true,
    73  							Elem:     &schema.Schema{Type: schema.TypeString},
    74  							Set:      schema.HashString,
    75  						},
    76  
    77  						"traffic_type": &schema.Schema{
    78  							Type:     schema.TypeString,
    79  							Optional: true,
    80  							Default:  "ingress",
    81  						},
    82  
    83  						"uuids": &schema.Schema{
    84  							Type:     schema.TypeMap,
    85  							Computed: true,
    86  						},
    87  					},
    88  				},
    89  			},
    90  
    91  			"parallelism": &schema.Schema{
    92  				Type:     schema.TypeInt,
    93  				Optional: true,
    94  				Default:  2,
    95  			},
    96  		},
    97  	}
    98  }
    99  
   100  func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error {
   101  	// Make sure all required parameters are there
   102  	if err := verifyNetworkACLParams(d); err != nil {
   103  		return err
   104  	}
   105  
   106  	// We need to set this upfront in order to be able to save a partial state
   107  	d.SetId(d.Get("acl_id").(string))
   108  
   109  	// Create all rules that are configured
   110  	if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 {
   111  		// Create an empty rule set to hold all newly created rules
   112  		rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set)
   113  
   114  		err := createNetworkACLRules(d, meta, rules, nrs)
   115  
   116  		// We need to update this first to preserve the correct state
   117  		d.Set("rule", rules)
   118  
   119  		if err != nil {
   120  			return err
   121  		}
   122  	}
   123  
   124  	return resourceCloudStackNetworkACLRuleRead(d, meta)
   125  }
   126  
   127  func createNetworkACLRules(d *schema.ResourceData, meta interface{}, rules *schema.Set, 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{}, d.Get("parallelism").(int))
   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 := createNetworkACLRule(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 createNetworkACLRule(d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
   164  	cs := meta.(*cloudstack.CloudStackClient)
   165  	uuids := rule["uuids"].(map[string]interface{})
   166  
   167  	// Make sure all required parameters are there
   168  	if err := verifyNetworkACLRuleParams(d, rule); err != nil {
   169  		return err
   170  	}
   171  
   172  	// Create a new parameter struct
   173  	p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string))
   174  
   175  	// Set the acl ID
   176  	p.SetAclid(d.Id())
   177  
   178  	// Set the action
   179  	p.SetAction(rule["action"].(string))
   180  
   181  	// Set the CIDR list
   182  	var cidrList []string
   183  	for _, cidr := range rule["cidr_list"].(*schema.Set).List() {
   184  		cidrList = append(cidrList, cidr.(string))
   185  	}
   186  	p.SetCidrlist(cidrList)
   187  
   188  	// Set the traffic type
   189  	p.SetTraffictype(rule["traffic_type"].(string))
   190  
   191  	// If the protocol is ICMP set the needed ICMP parameters
   192  	if rule["protocol"].(string) == "icmp" {
   193  		p.SetIcmptype(rule["icmp_type"].(int))
   194  		p.SetIcmpcode(rule["icmp_code"].(int))
   195  
   196  		r, err := Retry(4, retryableACLCreationFunc(cs, p))
   197  		if err != nil {
   198  			return err
   199  		}
   200  
   201  		uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id
   202  		rule["uuids"] = uuids
   203  	}
   204  
   205  	// If the protocol is ALL set the needed parameters
   206  	if rule["protocol"].(string) == "all" {
   207  		r, err := Retry(4, retryableACLCreationFunc(cs, p))
   208  		if err != nil {
   209  			return err
   210  		}
   211  
   212  		uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id
   213  		rule["uuids"] = uuids
   214  	}
   215  
   216  	// If protocol is TCP or UDP, loop through all ports
   217  	if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" {
   218  		if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   219  
   220  			// Create an empty schema.Set to hold all processed ports
   221  			ports := &schema.Set{F: schema.HashString}
   222  
   223  			for _, port := range ps.List() {
   224  				if _, ok := uuids[port.(string)]; ok {
   225  					ports.Add(port)
   226  					rule["ports"] = ports
   227  					continue
   228  				}
   229  
   230  				m := splitPorts.FindStringSubmatch(port.(string))
   231  
   232  				startPort, err := strconv.Atoi(m[1])
   233  				if err != nil {
   234  					return err
   235  				}
   236  
   237  				endPort := startPort
   238  				if m[2] != "" {
   239  					endPort, err = strconv.Atoi(m[2])
   240  					if err != nil {
   241  						return err
   242  					}
   243  				}
   244  
   245  				p.SetStartport(startPort)
   246  				p.SetEndport(endPort)
   247  
   248  				r, err := Retry(4, retryableACLCreationFunc(cs, p))
   249  				if err != nil {
   250  					return err
   251  				}
   252  
   253  				ports.Add(port)
   254  				rule["ports"] = ports
   255  
   256  				uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id
   257  				rule["uuids"] = uuids
   258  			}
   259  		}
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error {
   266  	cs := meta.(*cloudstack.CloudStackClient)
   267  
   268  	// Get all the rules from the running environment
   269  	p := cs.NetworkACL.NewListNetworkACLsParams()
   270  	p.SetAclid(d.Id())
   271  	p.SetListall(true)
   272  
   273  	l, err := cs.NetworkACL.ListNetworkACLs(p)
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	// Make a map of all the rules so we can easily find a rule
   279  	ruleMap := make(map[string]*cloudstack.NetworkACL, l.Count)
   280  	for _, r := range l.NetworkACLs {
   281  		ruleMap[r.Id] = r
   282  	}
   283  
   284  	// Create an empty schema.Set to hold all rules
   285  	rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set)
   286  
   287  	// Read all rules that are configured
   288  	if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
   289  		for _, rule := range rs.List() {
   290  			rule := rule.(map[string]interface{})
   291  			uuids := rule["uuids"].(map[string]interface{})
   292  
   293  			if rule["protocol"].(string) == "icmp" {
   294  				id, ok := uuids["icmp"]
   295  				if !ok {
   296  					continue
   297  				}
   298  
   299  				// Get the rule
   300  				r, ok := ruleMap[id.(string)]
   301  				if !ok {
   302  					delete(uuids, "icmp")
   303  					continue
   304  				}
   305  
   306  				// Delete the known rule so only unknown rules remain in the ruleMap
   307  				delete(ruleMap, id.(string))
   308  
   309  				// Create a set with all CIDR's
   310  				cidrs := &schema.Set{F: schema.HashString}
   311  				for _, cidr := range strings.Split(r.Cidrlist, ",") {
   312  					cidrs.Add(cidr)
   313  				}
   314  
   315  				// Update the values
   316  				rule["action"] = strings.ToLower(r.Action)
   317  				rule["protocol"] = r.Protocol
   318  				rule["icmp_type"] = r.Icmptype
   319  				rule["icmp_code"] = r.Icmpcode
   320  				rule["traffic_type"] = strings.ToLower(r.Traffictype)
   321  				rule["cidr_list"] = cidrs
   322  				rules.Add(rule)
   323  			}
   324  
   325  			if rule["protocol"].(string) == "all" {
   326  				id, ok := uuids["all"]
   327  				if !ok {
   328  					continue
   329  				}
   330  
   331  				// Get the rule
   332  				r, ok := ruleMap[id.(string)]
   333  				if !ok {
   334  					delete(uuids, "all")
   335  					continue
   336  				}
   337  
   338  				// Delete the known rule so only unknown rules remain in the ruleMap
   339  				delete(ruleMap, id.(string))
   340  
   341  				// Create a set with all CIDR's
   342  				cidrs := &schema.Set{F: schema.HashString}
   343  				for _, cidr := range strings.Split(r.Cidrlist, ",") {
   344  					cidrs.Add(cidr)
   345  				}
   346  
   347  				// Update the values
   348  				rule["action"] = strings.ToLower(r.Action)
   349  				rule["protocol"] = r.Protocol
   350  				rule["traffic_type"] = strings.ToLower(r.Traffictype)
   351  				rule["cidr_list"] = cidrs
   352  				rules.Add(rule)
   353  			}
   354  
   355  			// If protocol is tcp or udp, loop through all ports
   356  			if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" {
   357  				if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
   358  
   359  					// Create an empty schema.Set to hold all ports
   360  					ports := &schema.Set{F: schema.HashString}
   361  
   362  					// Loop through all ports and retrieve their info
   363  					for _, port := range ps.List() {
   364  						id, ok := uuids[port.(string)]
   365  						if !ok {
   366  							continue
   367  						}
   368  
   369  						// Get the rule
   370  						r, ok := ruleMap[id.(string)]
   371  						if !ok {
   372  							delete(uuids, port.(string))
   373  							continue
   374  						}
   375  
   376  						// Delete the known rule so only unknown rules remain in the ruleMap
   377  						delete(ruleMap, id.(string))
   378  
   379  						// Create a set with all CIDR's
   380  						cidrs := &schema.Set{F: schema.HashString}
   381  						for _, cidr := range strings.Split(r.Cidrlist, ",") {
   382  							cidrs.Add(cidr)
   383  						}
   384  
   385  						// Update the values
   386  						rule["action"] = strings.ToLower(r.Action)
   387  						rule["protocol"] = r.Protocol
   388  						rule["traffic_type"] = strings.ToLower(r.Traffictype)
   389  						rule["cidr_list"] = cidrs
   390  						ports.Add(port)
   391  					}
   392  
   393  					// If there is at least one port found, add this rule to the rules set
   394  					if ports.Len() > 0 {
   395  						rule["ports"] = ports
   396  						rules.Add(rule)
   397  					}
   398  				}
   399  			}
   400  		}
   401  	}
   402  
   403  	// If this is a managed firewall, add all unknown rules into dummy rules
   404  	managed := d.Get("managed").(bool)
   405  	if managed && len(ruleMap) > 0 {
   406  		for uuid := range ruleMap {
   407  			// We need to create and add a dummy value to a schema.Set as the
   408  			// cidr_list is a required field and thus needs a value
   409  			cidrs := &schema.Set{F: schema.HashString}
   410  			cidrs.Add(uuid)
   411  
   412  			// Make a dummy rule to hold the unknown UUID
   413  			rule := map[string]interface{}{
   414  				"cidr_list": cidrs,
   415  				"protocol":  uuid,
   416  				"uuids":     map[string]interface{}{uuid: uuid},
   417  			}
   418  
   419  			// Add the dummy rule to the rules set
   420  			rules.Add(rule)
   421  		}
   422  	}
   423  
   424  	if rules.Len() > 0 {
   425  		d.Set("rule", rules)
   426  	} else if !managed {
   427  		d.SetId("")
   428  	}
   429  
   430  	return nil
   431  }
   432  
   433  func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error {
   434  	// Make sure all required parameters are there
   435  	if err := verifyNetworkACLParams(d); err != nil {
   436  		return err
   437  	}
   438  
   439  	// Check if the rule set as a whole has changed
   440  	if d.HasChange("rule") {
   441  		o, n := d.GetChange("rule")
   442  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   443  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   444  
   445  		// We need to start with a rule set containing all the rules we
   446  		// already have and want to keep. Any rules that are not deleted
   447  		// correctly and any newly created rules, will be added to this
   448  		// set to make sure we end up in a consistent state
   449  		rules := o.(*schema.Set).Intersection(n.(*schema.Set))
   450  
   451  		// First loop through all the new rules and create (before destroy) them
   452  		if nrs.Len() > 0 {
   453  			err := createNetworkACLRules(d, meta, rules, nrs)
   454  
   455  			// We need to update this first to preserve the correct state
   456  			d.Set("rule", rules)
   457  
   458  			if err != nil {
   459  				return err
   460  			}
   461  		}
   462  
   463  		// Then loop through all the old rules and delete them
   464  		if ors.Len() > 0 {
   465  			err := deleteNetworkACLRules(d, meta, rules, ors)
   466  
   467  			// We need to update this first to preserve the correct state
   468  			d.Set("rule", rules)
   469  
   470  			if err != nil {
   471  				return err
   472  			}
   473  		}
   474  	}
   475  
   476  	return resourceCloudStackNetworkACLRuleRead(d, meta)
   477  }
   478  
   479  func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error {
   480  	// Create an empty rule set to hold all rules that where
   481  	// not deleted correctly
   482  	rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set)
   483  
   484  	// Delete all rules
   485  	if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 {
   486  		err := deleteNetworkACLRules(d, meta, rules, ors)
   487  
   488  		// We need to update this first to preserve the correct state
   489  		d.Set("rule", rules)
   490  
   491  		if err != nil {
   492  			return err
   493  		}
   494  	}
   495  
   496  	return nil
   497  }
   498  
   499  func deleteNetworkACLRules(
   500  	d *schema.ResourceData,
   501  	meta interface{},
   502  	rules *schema.Set,
   503  	ors *schema.Set) error {
   504  	var errs *multierror.Error
   505  
   506  	var wg sync.WaitGroup
   507  	wg.Add(ors.Len())
   508  
   509  	sem := make(chan struct{}, d.Get("parallelism").(int))
   510  	for _, rule := range ors.List() {
   511  		// Put a sleep here to avoid DoS'ing the API
   512  		time.Sleep(500 * time.Millisecond)
   513  
   514  		go func(rule map[string]interface{}) {
   515  			defer wg.Done()
   516  			sem <- struct{}{}
   517  
   518  			// Delete a single rule
   519  			err := deleteNetworkACLRule(d, meta, rule)
   520  
   521  			// If we have at least one UUID, we need to save the rule
   522  			if len(rule["uuids"].(map[string]interface{})) > 0 {
   523  				rules.Add(rule)
   524  			}
   525  
   526  			if err != nil {
   527  				errs = multierror.Append(errs, err)
   528  			}
   529  
   530  			<-sem
   531  		}(rule.(map[string]interface{}))
   532  	}
   533  
   534  	wg.Wait()
   535  
   536  	return errs.ErrorOrNil()
   537  }
   538  
   539  func deleteNetworkACLRule(
   540  	d *schema.ResourceData,
   541  	meta interface{},
   542  	rule map[string]interface{}) error {
   543  	cs := meta.(*cloudstack.CloudStackClient)
   544  	uuids := rule["uuids"].(map[string]interface{})
   545  
   546  	for k, id := range uuids {
   547  		// We don't care about the count here, so just continue
   548  		if k == "%" {
   549  			continue
   550  		}
   551  
   552  		// Create the parameter struct
   553  		p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string))
   554  
   555  		// Delete the rule
   556  		if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil {
   557  
   558  			// This is a very poor way to be told the ID does no longer exist :(
   559  			if strings.Contains(err.Error(), fmt.Sprintf(
   560  				"Invalid parameter id value=%s due to incorrect long value format, "+
   561  					"or entity does not exist", id.(string))) {
   562  				delete(uuids, k)
   563  				rule["uuids"] = uuids
   564  				continue
   565  			}
   566  
   567  			return err
   568  		}
   569  
   570  		// Delete the UUID of this rule
   571  		delete(uuids, k)
   572  		rule["uuids"] = uuids
   573  	}
   574  
   575  	return nil
   576  }
   577  
   578  func verifyNetworkACLParams(d *schema.ResourceData) error {
   579  	managed := d.Get("managed").(bool)
   580  	_, rules := d.GetOk("rule")
   581  
   582  	if !rules && !managed {
   583  		return fmt.Errorf(
   584  			"You must supply at least one 'rule' when not using the 'managed' firewall feature")
   585  	}
   586  
   587  	return nil
   588  }
   589  
   590  func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
   591  	action := rule["action"].(string)
   592  	if action != "allow" && action != "deny" {
   593  		return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values")
   594  	}
   595  
   596  	protocol := rule["protocol"].(string)
   597  	switch protocol {
   598  	case "icmp":
   599  		if _, ok := rule["icmp_type"]; !ok {
   600  			return fmt.Errorf(
   601  				"Parameter icmp_type is a required parameter when using protocol 'icmp'")
   602  		}
   603  		if _, ok := rule["icmp_code"]; !ok {
   604  			return fmt.Errorf(
   605  				"Parameter icmp_code is a required parameter when using protocol 'icmp'")
   606  		}
   607  	case "all":
   608  		// No additional test are needed, so just leave this empty...
   609  	case "tcp", "udp":
   610  		if ports, ok := rule["ports"].(*schema.Set); ok {
   611  			for _, port := range ports.List() {
   612  				m := splitPorts.FindStringSubmatch(port.(string))
   613  				if m == nil {
   614  					return fmt.Errorf(
   615  						"%q is not a valid port value. Valid options are '80' or '80-90'", port.(string))
   616  				}
   617  			}
   618  		} else {
   619  			return fmt.Errorf(
   620  				"Parameter ports is a required parameter when *not* using protocol 'icmp'")
   621  		}
   622  	default:
   623  		_, err := strconv.ParseInt(protocol, 0, 0)
   624  		if err != nil {
   625  			return fmt.Errorf(
   626  				"%q is not a valid protocol. Valid options are 'tcp', 'udp', "+
   627  					"'icmp', 'all' or a valid protocol number", protocol)
   628  		}
   629  	}
   630  
   631  	traffic := rule["traffic_type"].(string)
   632  	if traffic != "ingress" && traffic != "egress" {
   633  		return fmt.Errorf(
   634  			"Parameter traffic_type only accepts 'ingress' or 'egress' as values")
   635  	}
   636  
   637  	return nil
   638  }
   639  
   640  func retryableACLCreationFunc(
   641  	cs *cloudstack.CloudStackClient,
   642  	p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) {
   643  	return func() (interface{}, error) {
   644  		r, err := cs.NetworkACL.CreateNetworkACL(p)
   645  		if err != nil {
   646  			return nil, err
   647  		}
   648  		return r, nil
   649  	}
   650  }