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