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