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