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

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