github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/builtin/providers/cloudstack/resource_cloudstack_port_forward.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"strconv"
     9  	"strings"
    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 resourceCloudStackPortForward() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceCloudStackPortForwardCreate,
    19  		Read:   resourceCloudStackPortForwardRead,
    20  		Update: resourceCloudStackPortForwardUpdate,
    21  		Delete: resourceCloudStackPortForwardDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"ipaddress": &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  			"forward": &schema.Schema{
    37  				Type:     schema.TypeSet,
    38  				Required: true,
    39  				Elem: &schema.Resource{
    40  					Schema: map[string]*schema.Schema{
    41  						"protocol": &schema.Schema{
    42  							Type:     schema.TypeString,
    43  							Required: true,
    44  						},
    45  
    46  						"private_port": &schema.Schema{
    47  							Type:     schema.TypeInt,
    48  							Required: true,
    49  						},
    50  
    51  						"public_port": &schema.Schema{
    52  							Type:     schema.TypeInt,
    53  							Required: true,
    54  						},
    55  
    56  						"virtual_machine": &schema.Schema{
    57  							Type:     schema.TypeString,
    58  							Required: true,
    59  						},
    60  
    61  						"uuid": &schema.Schema{
    62  							Type:     schema.TypeString,
    63  							Computed: true,
    64  						},
    65  					},
    66  				},
    67  			},
    68  		},
    69  	}
    70  }
    71  
    72  func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error {
    73  	cs := meta.(*cloudstack.CloudStackClient)
    74  
    75  	// Retrieve the ipaddress ID
    76  	ipaddressid, e := retrieveID(cs, "ipaddress", d.Get("ipaddress").(string))
    77  	if e != nil {
    78  		return e.Error()
    79  	}
    80  
    81  	// We need to set this upfront in order to be able to save a partial state
    82  	d.SetId(ipaddressid)
    83  
    84  	// Create all forwards that are configured
    85  	if nrs := d.Get("forward").(*schema.Set); nrs.Len() > 0 {
    86  		// Create an empty schema.Set to hold all forwards
    87  		forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set)
    88  
    89  		err := createPortForwards(d, meta, forwards, nrs)
    90  
    91  		// We need to update this first to preserve the correct state
    92  		d.Set("forward", forwards)
    93  
    94  		if err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	return resourceCloudStackPortForwardRead(d, meta)
   100  }
   101  
   102  func createPortForwards(
   103  	d *schema.ResourceData,
   104  	meta interface{},
   105  	forwards *schema.Set,
   106  	nrs *schema.Set) error {
   107  	var errs *multierror.Error
   108  
   109  	var wg sync.WaitGroup
   110  	wg.Add(nrs.Len())
   111  
   112  	sem := make(chan struct{}, 10)
   113  	for _, forward := range nrs.List() {
   114  		// Put in a tiny sleep here to avoid DoS'ing the API
   115  		time.Sleep(500 * time.Millisecond)
   116  
   117  		go func(forward map[string]interface{}) {
   118  			defer wg.Done()
   119  			sem <- struct{}{}
   120  
   121  			// Create a single forward
   122  			err := createPortForward(d, meta, forward)
   123  
   124  			// If we have a UUID, we need to save the forward
   125  			if forward["uuid"].(string) != "" {
   126  				forwards.Add(forward)
   127  			}
   128  
   129  			if err != nil {
   130  				errs = multierror.Append(errs, err)
   131  			}
   132  
   133  			<-sem
   134  		}(forward.(map[string]interface{}))
   135  	}
   136  
   137  	wg.Wait()
   138  
   139  	return errs.ErrorOrNil()
   140  }
   141  func createPortForward(
   142  	d *schema.ResourceData,
   143  	meta interface{},
   144  	forward map[string]interface{}) error {
   145  	cs := meta.(*cloudstack.CloudStackClient)
   146  
   147  	// Make sure all required parameters are there
   148  	if err := verifyPortForwardParams(d, forward); err != nil {
   149  		return err
   150  	}
   151  
   152  	// Retrieve the virtual_machine ID
   153  	virtualmachineid, e := retrieveID(cs, "virtual_machine", forward["virtual_machine"].(string))
   154  	if e != nil {
   155  		return e.Error()
   156  	}
   157  
   158  	vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	// Create a new parameter struct
   164  	p := cs.Firewall.NewCreatePortForwardingRuleParams(d.Id(), forward["private_port"].(int),
   165  		forward["protocol"].(string), forward["public_port"].(int), vm.Id)
   166  
   167  	// Set the network ID of the default network, needed when public IP address
   168  	// is not associated with any Guest network yet (VPC case)
   169  	p.SetNetworkid(vm.Nic[0].Networkid)
   170  
   171  	// Do not open the firewall automatically in any case
   172  	p.SetOpenfirewall(false)
   173  
   174  	r, err := cs.Firewall.CreatePortForwardingRule(p)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	forward["uuid"] = r.Id
   180  
   181  	return nil
   182  }
   183  
   184  func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error {
   185  	cs := meta.(*cloudstack.CloudStackClient)
   186  
   187  	// Get all the forwards from the running environment
   188  	p := cs.Firewall.NewListPortForwardingRulesParams()
   189  	p.SetIpaddressid(d.Id())
   190  	p.SetListall(true)
   191  
   192  	l, err := cs.Firewall.ListPortForwardingRules(p)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	// Make a map of all the forwards so we can easily find a forward
   198  	forwardMap := make(map[string]*cloudstack.PortForwardingRule, l.Count)
   199  	for _, f := range l.PortForwardingRules {
   200  		forwardMap[f.Id] = f
   201  	}
   202  
   203  	// Create an empty schema.Set to hold all forwards
   204  	forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set)
   205  
   206  	// Read all forwards that are configured
   207  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   208  		for _, forward := range rs.List() {
   209  			forward := forward.(map[string]interface{})
   210  
   211  			id, ok := forward["uuid"]
   212  			if !ok || id.(string) == "" {
   213  				continue
   214  			}
   215  
   216  			// Get the forward
   217  			f, ok := forwardMap[id.(string)]
   218  			if !ok {
   219  				forward["uuid"] = ""
   220  				continue
   221  			}
   222  
   223  			// Delete the known rule so only unknown rules remain in the ruleMap
   224  			delete(forwardMap, id.(string))
   225  
   226  			privPort, err := strconv.Atoi(f.Privateport)
   227  			if err != nil {
   228  				return err
   229  			}
   230  
   231  			pubPort, err := strconv.Atoi(f.Publicport)
   232  			if err != nil {
   233  				return err
   234  			}
   235  
   236  			// Update the values
   237  			forward["protocol"] = f.Protocol
   238  			forward["private_port"] = privPort
   239  			forward["public_port"] = pubPort
   240  
   241  			if isID(forward["virtual_machine"].(string)) {
   242  				forward["virtual_machine"] = f.Virtualmachineid
   243  			} else {
   244  				forward["virtual_machine"] = f.Virtualmachinename
   245  			}
   246  
   247  			forwards.Add(forward)
   248  		}
   249  	}
   250  
   251  	// If this is a managed resource, add all unknown forwards to dummy forwards
   252  	managed := d.Get("managed").(bool)
   253  	if managed && len(forwardMap) > 0 {
   254  		for uuid := range forwardMap {
   255  			// Make a dummy forward to hold the unknown UUID
   256  			forward := map[string]interface{}{
   257  				"protocol":        uuid,
   258  				"private_port":    0,
   259  				"public_port":     0,
   260  				"virtual_machine": uuid,
   261  				"uuid":            uuid,
   262  			}
   263  
   264  			// Add the dummy forward to the forwards set
   265  			forwards.Add(forward)
   266  		}
   267  	}
   268  
   269  	if forwards.Len() > 0 {
   270  		d.Set("forward", forwards)
   271  	} else if !managed {
   272  		d.SetId("")
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error {
   279  	// Check if the forward set as a whole has changed
   280  	if d.HasChange("forward") {
   281  		o, n := d.GetChange("forward")
   282  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   283  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   284  
   285  		// We need to start with a rule set containing all the rules we
   286  		// already have and want to keep. Any rules that are not deleted
   287  		// correctly and any newly created rules, will be added to this
   288  		// set to make sure we end up in a consistent state
   289  		forwards := o.(*schema.Set).Intersection(n.(*schema.Set))
   290  
   291  		// First loop through all the new forwards and create (before destroy) them
   292  		if nrs.Len() > 0 {
   293  			err := createPortForwards(d, meta, forwards, nrs)
   294  
   295  			// We need to update this first to preserve the correct state
   296  			d.Set("forward", forwards)
   297  
   298  			if err != nil {
   299  				return err
   300  			}
   301  		}
   302  
   303  		// Then loop through all the old forwards and delete them
   304  		if ors.Len() > 0 {
   305  			err := deletePortForwards(d, meta, forwards, ors)
   306  
   307  			// We need to update this first to preserve the correct state
   308  			d.Set("forward", forwards)
   309  
   310  			if err != nil {
   311  				return err
   312  			}
   313  		}
   314  	}
   315  
   316  	return resourceCloudStackPortForwardRead(d, meta)
   317  }
   318  
   319  func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error {
   320  	// Create an empty rule set to hold all rules that where
   321  	// not deleted correctly
   322  	forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set)
   323  
   324  	// Delete all forwards
   325  	if ors := d.Get("forward").(*schema.Set); ors.Len() > 0 {
   326  		err := deletePortForwards(d, meta, forwards, ors)
   327  
   328  		// We need to update this first to preserve the correct state
   329  		d.Set("forward", forwards)
   330  
   331  		if err != nil {
   332  			return err
   333  		}
   334  	}
   335  
   336  	return nil
   337  }
   338  
   339  func deletePortForwards(
   340  	d *schema.ResourceData,
   341  	meta interface{},
   342  	forwards *schema.Set,
   343  	ors *schema.Set) error {
   344  	var errs *multierror.Error
   345  
   346  	var wg sync.WaitGroup
   347  	wg.Add(ors.Len())
   348  
   349  	sem := make(chan struct{}, 10)
   350  	for _, forward := range ors.List() {
   351  		// Put a sleep here to avoid DoS'ing the API
   352  		time.Sleep(500 * time.Millisecond)
   353  
   354  		go func(forward map[string]interface{}) {
   355  			defer wg.Done()
   356  			sem <- struct{}{}
   357  
   358  			// Delete a single forward
   359  			err := deletePortForward(d, meta, forward)
   360  
   361  			// If we have a UUID, we need to save the forward
   362  			if forward["uuid"].(string) != "" {
   363  				forwards.Add(forward)
   364  			}
   365  
   366  			if err != nil {
   367  				errs = multierror.Append(errs, err)
   368  			}
   369  
   370  			<-sem
   371  		}(forward.(map[string]interface{}))
   372  	}
   373  
   374  	wg.Wait()
   375  
   376  	return errs.ErrorOrNil()
   377  }
   378  
   379  func deletePortForward(
   380  	d *schema.ResourceData,
   381  	meta interface{},
   382  	forward map[string]interface{}) error {
   383  	cs := meta.(*cloudstack.CloudStackClient)
   384  
   385  	// Create the parameter struct
   386  	p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string))
   387  
   388  	// Delete the forward
   389  	if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil {
   390  		// This is a very poor way to be told the ID does no longer exist :(
   391  		if !strings.Contains(err.Error(), fmt.Sprintf(
   392  			"Invalid parameter id value=%s due to incorrect long value format, "+
   393  				"or entity does not exist", forward["uuid"].(string))) {
   394  			return err
   395  		}
   396  	}
   397  
   398  	// Empty the UUID of this rule
   399  	forward["uuid"] = ""
   400  
   401  	return nil
   402  }
   403  
   404  func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error {
   405  	protocol := forward["protocol"].(string)
   406  	if protocol != "tcp" && protocol != "udp" {
   407  		return fmt.Errorf(
   408  			"%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol)
   409  	}
   410  	return nil
   411  }