github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/cloudstack/resource_cloudstack_port_forward.go (about)

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