github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/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  			"managed": &schema.Schema{
    30  				Type:     schema.TypeBool,
    31  				Optional: true,
    32  				Default:  false,
    33  			},
    34  
    35  			"forward": &schema.Schema{
    36  				Type:     schema.TypeSet,
    37  				Required: true,
    38  				Elem: &schema.Resource{
    39  					Schema: map[string]*schema.Schema{
    40  						"protocol": &schema.Schema{
    41  							Type:     schema.TypeString,
    42  							Required: true,
    43  						},
    44  
    45  						"private_port": &schema.Schema{
    46  							Type:     schema.TypeInt,
    47  							Required: true,
    48  						},
    49  
    50  						"public_port": &schema.Schema{
    51  							Type:     schema.TypeInt,
    52  							Required: true,
    53  						},
    54  
    55  						"virtual_machine": &schema.Schema{
    56  							Type:     schema.TypeString,
    57  							Required: true,
    58  						},
    59  
    60  						"uuid": &schema.Schema{
    61  							Type:     schema.TypeString,
    62  							Computed: true,
    63  						},
    64  					},
    65  				},
    66  				Set: resourceCloudStackPortForwardHash,
    67  			},
    68  		},
    69  	}
    70  }
    71  
    72  func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error {
    73  	cs := meta.(*cloudstack.CloudStackClient)
    74  
    75  	// Retrieve the ipaddress UUID
    76  	ipaddressid, e := retrieveUUID(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 rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
    86  
    87  		// Create an empty schema.Set to hold all forwards
    88  		forwards := &schema.Set{
    89  			F: resourceCloudStackPortForwardHash,
    90  		}
    91  
    92  		for _, forward := range rs.List() {
    93  			// Create a single forward
    94  			err := resourceCloudStackPortForwardCreateForward(d, meta, forward.(map[string]interface{}))
    95  
    96  			// We need to update this first to preserve the correct state
    97  			forwards.Add(forward)
    98  			d.Set("forward", forwards)
    99  
   100  			if err != nil {
   101  				return err
   102  			}
   103  		}
   104  	}
   105  
   106  	return resourceCloudStackPortForwardRead(d, meta)
   107  }
   108  
   109  func resourceCloudStackPortForwardCreateForward(
   110  	d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error {
   111  	cs := meta.(*cloudstack.CloudStackClient)
   112  
   113  	// Make sure all required parameters are there
   114  	if err := verifyPortForwardParams(d, forward); err != nil {
   115  		return err
   116  	}
   117  
   118  	// Retrieve the virtual_machine UUID
   119  	vm, _, err := cs.VirtualMachine.GetVirtualMachineByName(forward["virtual_machine"].(string))
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	// Create a new parameter struct
   125  	p := cs.Firewall.NewCreatePortForwardingRuleParams(d.Id(), forward["private_port"].(int),
   126  		forward["protocol"].(string), forward["public_port"].(int), vm.Id)
   127  
   128  	// Set the network ID of the default network, needed when public IP address
   129  	// is not associated with any Guest network yet (VPC case)
   130  	p.SetNetworkid(vm.Nic[0].Networkid)
   131  
   132  	// Do not open the firewall automatically in any case
   133  	p.SetOpenfirewall(false)
   134  
   135  	r, err := cs.Firewall.CreatePortForwardingRule(p)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	forward["uuid"] = r.Id
   141  
   142  	return nil
   143  }
   144  
   145  func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error {
   146  	cs := meta.(*cloudstack.CloudStackClient)
   147  
   148  	// Create an empty schema.Set to hold all forwards
   149  	forwards := &schema.Set{
   150  		F: resourceCloudStackPortForwardHash,
   151  	}
   152  
   153  	// Read all forwards that are configured
   154  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   155  		for _, forward := range rs.List() {
   156  			forward := forward.(map[string]interface{})
   157  
   158  			id, ok := forward["uuid"]
   159  			if !ok || id.(string) == "" {
   160  				continue
   161  			}
   162  
   163  			// Get the forward
   164  			r, count, err := cs.Firewall.GetPortForwardingRuleByID(id.(string))
   165  			// If the count == 0, there is no object found for this UUID
   166  			if err != nil {
   167  				if count == 0 {
   168  					forward["uuid"] = ""
   169  					continue
   170  				}
   171  
   172  				return err
   173  			}
   174  
   175  			privPort, err := strconv.Atoi(r.Privateport)
   176  			if err != nil {
   177  				return err
   178  			}
   179  
   180  			pubPort, err := strconv.Atoi(r.Publicport)
   181  			if err != nil {
   182  				return err
   183  			}
   184  
   185  			// Update the values
   186  			forward["protocol"] = r.Protocol
   187  			forward["private_port"] = privPort
   188  			forward["public_port"] = pubPort
   189  			forward["virtual_machine"] = r.Virtualmachinename
   190  			forwards.Add(forward)
   191  		}
   192  	}
   193  
   194  	// If this is a managed resource, add all unknown forwards to dummy forwards
   195  	managed := d.Get("managed").(bool)
   196  	if managed {
   197  		// Get all the forwards from the running environment
   198  		p := cs.Firewall.NewListPortForwardingRulesParams()
   199  		p.SetIpaddressid(d.Id())
   200  		p.SetListall(true)
   201  
   202  		r, err := cs.Firewall.ListPortForwardingRules(p)
   203  		if err != nil {
   204  			return err
   205  		}
   206  
   207  		// Add all UUIDs to the uuids map
   208  		uuids := make(map[string]interface{}, len(r.PortForwardingRules))
   209  		for _, r := range r.PortForwardingRules {
   210  			uuids[r.Id] = r.Id
   211  		}
   212  
   213  		// Delete all expected UUIDs from the uuids map
   214  		for _, forward := range forwards.List() {
   215  			forward := forward.(map[string]interface{})
   216  
   217  			for _, id := range forward["uuids"].(map[string]interface{}) {
   218  				delete(uuids, id.(string))
   219  			}
   220  		}
   221  
   222  		for uuid, _ := range uuids {
   223  			// Make a dummy forward to hold the unknown UUID
   224  			forward := map[string]interface{}{
   225  				"protocol":        "N/A",
   226  				"private_port":    0,
   227  				"public_port":     0,
   228  				"virtual_machine": uuid,
   229  				"uuid":            uuid,
   230  			}
   231  
   232  			// Add the dummy forward to the forwards set
   233  			forwards.Add(forward)
   234  		}
   235  	}
   236  
   237  	if forwards.Len() > 0 {
   238  		d.Set("forward", forwards)
   239  	} else if !managed {
   240  		d.SetId("")
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error {
   247  	// Check if the forward set as a whole has changed
   248  	if d.HasChange("forward") {
   249  		o, n := d.GetChange("forward")
   250  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   251  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   252  
   253  		// Now first loop through all the old forwards and delete any obsolete ones
   254  		for _, forward := range ors.List() {
   255  			// Delete the forward as it no longer exists in the config
   256  			err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
   257  			if err != nil {
   258  				return err
   259  			}
   260  		}
   261  
   262  		// Make sure we save the state of the currently configured forwards
   263  		forwards := o.(*schema.Set).Intersection(n.(*schema.Set))
   264  		d.Set("forward", forwards)
   265  
   266  		// Then loop through all the currently configured forwards and create the new ones
   267  		for _, forward := range nrs.List() {
   268  			err := resourceCloudStackPortForwardCreateForward(
   269  				d, meta, forward.(map[string]interface{}))
   270  
   271  			// We need to update this first to preserve the correct state
   272  			forwards.Add(forward)
   273  			d.Set("forward", forwards)
   274  
   275  			if err != nil {
   276  				return err
   277  			}
   278  		}
   279  	}
   280  
   281  	return resourceCloudStackPortForwardRead(d, meta)
   282  }
   283  
   284  func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error {
   285  	// Delete all forwards
   286  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   287  		for _, forward := range rs.List() {
   288  			// Delete a single forward
   289  			err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
   290  
   291  			// We need to update this first to preserve the correct state
   292  			d.Set("forward", rs)
   293  
   294  			if err != nil {
   295  				return err
   296  			}
   297  		}
   298  	}
   299  
   300  	return nil
   301  }
   302  
   303  func resourceCloudStackPortForwardDeleteForward(
   304  	d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error {
   305  	cs := meta.(*cloudstack.CloudStackClient)
   306  
   307  	// Create the parameter struct
   308  	p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string))
   309  
   310  	// Delete the forward
   311  	if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil {
   312  		// This is a very poor way to be told the UUID does no longer exist :(
   313  		if !strings.Contains(err.Error(), fmt.Sprintf(
   314  			"Invalid parameter id value=%s due to incorrect long value format, "+
   315  				"or entity does not exist", forward["uuid"].(string))) {
   316  			return err
   317  		}
   318  	}
   319  
   320  	forward["uuid"] = ""
   321  
   322  	return nil
   323  }
   324  
   325  func resourceCloudStackPortForwardHash(v interface{}) int {
   326  	var buf bytes.Buffer
   327  	m := v.(map[string]interface{})
   328  	buf.WriteString(fmt.Sprintf(
   329  		"%s-%d-%d-%s",
   330  		m["protocol"].(string),
   331  		m["private_port"].(int),
   332  		m["public_port"].(int),
   333  		m["virtual_machine"].(string)))
   334  
   335  	return hashcode.String(buf.String())
   336  }
   337  
   338  func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error {
   339  	protocol := forward["protocol"].(string)
   340  	if protocol != "tcp" && protocol != "udp" {
   341  		return fmt.Errorf(
   342  			"%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol)
   343  	}
   344  	return nil
   345  }