github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go (about)

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