github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/builtin/providers/cloudstack/resource_cloudstack_port_forward.go (about)

     1  package cloudstack
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/helper/hashcode"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"github.com/xanzy/go-cloudstack/cloudstack"
    13  )
    14  
    15  func resourceCloudStackPortForward() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceCloudStackPortForwardCreate,
    18  		Read:   resourceCloudStackPortForwardRead,
    19  		Update: resourceCloudStackPortForwardUpdate,
    20  		Delete: resourceCloudStackPortForwardDelete,
    21  
    22  		Schema: map[string]*schema.Schema{
    23  			"ipaddress": &schema.Schema{
    24  				Type:     schema.TypeString,
    25  				Required: true,
    26  				ForceNew: true,
    27  			},
    28  
    29  			"forward": &schema.Schema{
    30  				Type:     schema.TypeSet,
    31  				Required: true,
    32  				Elem: &schema.Resource{
    33  					Schema: map[string]*schema.Schema{
    34  						"protocol": &schema.Schema{
    35  							Type:     schema.TypeString,
    36  							Required: true,
    37  						},
    38  
    39  						"private_port": &schema.Schema{
    40  							Type:     schema.TypeInt,
    41  							Required: true,
    42  						},
    43  
    44  						"public_port": &schema.Schema{
    45  							Type:     schema.TypeInt,
    46  							Required: true,
    47  						},
    48  
    49  						"virtual_machine": &schema.Schema{
    50  							Type:     schema.TypeString,
    51  							Required: true,
    52  						},
    53  
    54  						"uuid": &schema.Schema{
    55  							Type:     schema.TypeString,
    56  							Computed: true,
    57  						},
    58  					},
    59  				},
    60  				Set: resourceCloudStackPortForwardHash,
    61  			},
    62  		},
    63  	}
    64  }
    65  
    66  func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error {
    67  	cs := meta.(*cloudstack.CloudStackClient)
    68  
    69  	// Retrieve the ipaddress UUID
    70  	ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
    71  	if e != nil {
    72  		return e.Error()
    73  	}
    74  
    75  	// We need to set this upfront in order to be able to save a partial state
    76  	d.SetId(d.Get("ipaddress").(string))
    77  
    78  	// Create all forwards that are configured
    79  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
    80  
    81  		// Create an empty schema.Set to hold all forwards
    82  		forwards := &schema.Set{
    83  			F: resourceCloudStackPortForwardHash,
    84  		}
    85  
    86  		for _, forward := range rs.List() {
    87  			// Create a single forward
    88  			err := resourceCloudStackPortForwardCreateForward(d, meta, ipaddressid, forward.(map[string]interface{}))
    89  
    90  			// We need to update this first to preserve the correct state
    91  			forwards.Add(forward)
    92  			d.Set("forward", forwards)
    93  
    94  			if err != nil {
    95  				return err
    96  			}
    97  		}
    98  	}
    99  
   100  	return resourceCloudStackPortForwardRead(d, meta)
   101  }
   102  
   103  func resourceCloudStackPortForwardCreateForward(
   104  	d *schema.ResourceData, meta interface{}, ipaddressid string, forward map[string]interface{}) error {
   105  	cs := meta.(*cloudstack.CloudStackClient)
   106  
   107  	// Make sure all required parameters are there
   108  	if err := verifyPortForwardParams(d, forward); err != nil {
   109  		return err
   110  	}
   111  
   112  	// Retrieve the virtual_machine UUID
   113  	virtualmachineid, e := retrieveUUID(cs, "virtual_machine", forward["virtual_machine"].(string))
   114  	if e != nil {
   115  		return e.Error()
   116  	}
   117  
   118  	// Create a new parameter struct
   119  	p := cs.Firewall.NewCreatePortForwardingRuleParams(ipaddressid, forward["private_port"].(int),
   120  		forward["protocol"].(string), forward["public_port"].(int), virtualmachineid)
   121  
   122  	// Do not open the firewall automatically in any case
   123  	p.SetOpenfirewall(false)
   124  
   125  	r, err := cs.Firewall.CreatePortForwardingRule(p)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	forward["uuid"] = r.Id
   131  
   132  	return nil
   133  }
   134  
   135  func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error {
   136  	cs := meta.(*cloudstack.CloudStackClient)
   137  
   138  	// Create an empty schema.Set to hold all forwards
   139  	forwards := &schema.Set{
   140  		F: resourceCloudStackPortForwardHash,
   141  	}
   142  
   143  	// Read all forwards that are configured
   144  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   145  		for _, forward := range rs.List() {
   146  			forward := forward.(map[string]interface{})
   147  
   148  			id, ok := forward["uuid"]
   149  			if !ok || id.(string) == "" {
   150  				continue
   151  			}
   152  
   153  			// Get the forward
   154  			r, count, err := cs.Firewall.GetPortForwardingRuleByID(id.(string))
   155  			// If the count == 0, there is no object found for this UUID
   156  			if err != nil {
   157  				if count != 0 {
   158  					continue
   159  				}
   160  
   161  				return err
   162  			}
   163  
   164  			privPort, err := strconv.Atoi(r.Privateport)
   165  			if err != nil {
   166  				return err
   167  			}
   168  
   169  			pubPort, err := strconv.Atoi(r.Publicport)
   170  			if err != nil {
   171  				return err
   172  			}
   173  
   174  			// Update the values
   175  			forward["protocol"] = r.Protocol
   176  			forward["private_port"] = privPort
   177  			forward["public_port"] = pubPort
   178  			forward["virtual_machine"] = r.Virtualmachinename
   179  			forwards.Add(forward)
   180  		}
   181  	}
   182  
   183  	if forwards.Len() > 0 {
   184  		d.Set("forward", forwards)
   185  	} else {
   186  		d.SetId("")
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error {
   193  	cs := meta.(*cloudstack.CloudStackClient)
   194  
   195  	// Retrieve the ipaddress UUID
   196  	ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string))
   197  	if e != nil {
   198  		return e.Error()
   199  	}
   200  
   201  	// Check if the forward set as a whole has changed
   202  	if d.HasChange("forward") {
   203  		o, n := d.GetChange("forward")
   204  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   205  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   206  
   207  		// Now first loop through all the old forwards and delete any obsolete ones
   208  		for _, forward := range ors.List() {
   209  			// Delete the forward as it no longer exists in the config
   210  			err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
   211  			if err != nil {
   212  				return err
   213  			}
   214  		}
   215  
   216  		// Make sure we save the state of the currently configured forwards
   217  		forwards := o.(*schema.Set).Intersection(n.(*schema.Set))
   218  		d.Set("forward", forwards)
   219  
   220  		// Then loop through al the currently configured forwards and create the new ones
   221  		for _, forward := range nrs.List() {
   222  			err := resourceCloudStackPortForwardCreateForward(
   223  				d, meta, ipaddressid, forward.(map[string]interface{}))
   224  
   225  			// We need to update this first to preserve the correct state
   226  			forwards.Add(forward)
   227  			d.Set("forward", forwards)
   228  
   229  			if err != nil {
   230  				return err
   231  			}
   232  		}
   233  	}
   234  
   235  	return resourceCloudStackPortForwardRead(d, meta)
   236  }
   237  
   238  func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error {
   239  	// Delete all forwards
   240  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   241  		for _, forward := range rs.List() {
   242  			// Delete a single forward
   243  			err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
   244  
   245  			// We need to update this first to preserve the correct state
   246  			d.Set("forward", rs)
   247  
   248  			if err != nil {
   249  				return err
   250  			}
   251  		}
   252  	}
   253  
   254  	return nil
   255  }
   256  
   257  func resourceCloudStackPortForwardDeleteForward(
   258  	d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error {
   259  	cs := meta.(*cloudstack.CloudStackClient)
   260  
   261  	// Create the parameter struct
   262  	p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string))
   263  
   264  	// Delete the forward
   265  	if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil {
   266  		// This is a very poor way to be told the UUID does no longer exist :(
   267  		if !strings.Contains(err.Error(), fmt.Sprintf(
   268  			"Invalid parameter id value=%s due to incorrect long value format, "+
   269  				"or entity does not exist", forward["uuid"].(string))) {
   270  			return err
   271  		}
   272  	}
   273  
   274  	forward["uuid"] = ""
   275  
   276  	return nil
   277  }
   278  
   279  func resourceCloudStackPortForwardHash(v interface{}) int {
   280  	var buf bytes.Buffer
   281  	m := v.(map[string]interface{})
   282  	buf.WriteString(fmt.Sprintf(
   283  		"%s-%d-%d-%s",
   284  		m["protocol"].(string),
   285  		m["private_port"].(int),
   286  		m["public_port"].(int),
   287  		m["virtual_machine"].(string)))
   288  
   289  	return hashcode.String(buf.String())
   290  }
   291  
   292  func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error {
   293  	protocol := forward["protocol"].(string)
   294  	if protocol != "tcp" && protocol != "udp" {
   295  		return fmt.Errorf(
   296  			"%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol)
   297  	}
   298  	return nil
   299  }