github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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 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 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 ID
   119  	virtualmachineid, e := retrieveID(cs, "virtual_machine", forward["virtual_machine"].(string))
   120  	if e != nil {
   121  		return e.Error()
   122  	}
   123  
   124  	vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	// Create a new parameter struct
   130  	p := cs.Firewall.NewCreatePortForwardingRuleParams(d.Id(), forward["private_port"].(int),
   131  		forward["protocol"].(string), forward["public_port"].(int), vm.Id)
   132  
   133  	// Set the network ID of the default network, needed when public IP address
   134  	// is not associated with any Guest network yet (VPC case)
   135  	p.SetNetworkid(vm.Nic[0].Networkid)
   136  
   137  	// Do not open the firewall automatically in any case
   138  	p.SetOpenfirewall(false)
   139  
   140  	r, err := cs.Firewall.CreatePortForwardingRule(p)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	forward["uuid"] = r.Id
   146  
   147  	return nil
   148  }
   149  
   150  func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error {
   151  	cs := meta.(*cloudstack.CloudStackClient)
   152  
   153  	// Create an empty schema.Set to hold all forwards
   154  	forwards := &schema.Set{
   155  		F: resourceCloudStackPortForwardHash,
   156  	}
   157  
   158  	// Read all forwards that are configured
   159  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   160  		for _, forward := range rs.List() {
   161  			forward := forward.(map[string]interface{})
   162  
   163  			id, ok := forward["uuid"]
   164  			if !ok || id.(string) == "" {
   165  				continue
   166  			}
   167  
   168  			// Get the forward
   169  			r, count, err := cs.Firewall.GetPortForwardingRuleByID(id.(string))
   170  			// If the count == 0, there is no object found for this ID
   171  			if err != nil {
   172  				if count == 0 {
   173  					forward["uuid"] = ""
   174  					continue
   175  				}
   176  
   177  				return err
   178  			}
   179  
   180  			privPort, err := strconv.Atoi(r.Privateport)
   181  			if err != nil {
   182  				return err
   183  			}
   184  
   185  			pubPort, err := strconv.Atoi(r.Publicport)
   186  			if err != nil {
   187  				return err
   188  			}
   189  
   190  			// Update the values
   191  			forward["protocol"] = r.Protocol
   192  			forward["private_port"] = privPort
   193  			forward["public_port"] = pubPort
   194  
   195  			if isID(forward["virtual_machine"].(string)) {
   196  				forward["virtual_machine"] = r.Virtualmachineid
   197  			} else {
   198  				forward["virtual_machine"] = r.Virtualmachinename
   199  			}
   200  
   201  			forwards.Add(forward)
   202  		}
   203  	}
   204  
   205  	// If this is a managed resource, add all unknown forwards to dummy forwards
   206  	managed := d.Get("managed").(bool)
   207  	if managed {
   208  		// Get all the forwards from the running environment
   209  		p := cs.Firewall.NewListPortForwardingRulesParams()
   210  		p.SetIpaddressid(d.Id())
   211  		p.SetListall(true)
   212  
   213  		r, err := cs.Firewall.ListPortForwardingRules(p)
   214  		if err != nil {
   215  			return err
   216  		}
   217  
   218  		// Add all UUIDs to the uuids map
   219  		uuids := make(map[string]interface{}, len(r.PortForwardingRules))
   220  		for _, r := range r.PortForwardingRules {
   221  			uuids[r.Id] = r.Id
   222  		}
   223  
   224  		// Delete all expected UUIDs from the uuids map
   225  		for _, forward := range forwards.List() {
   226  			forward := forward.(map[string]interface{})
   227  			delete(uuids, forward["uuid"].(string))
   228  		}
   229  
   230  		for uuid := range uuids {
   231  			// Make a dummy forward to hold the unknown UUID
   232  			forward := map[string]interface{}{
   233  				"protocol":        "N/A",
   234  				"private_port":    0,
   235  				"public_port":     0,
   236  				"virtual_machine": uuid,
   237  				"uuid":            uuid,
   238  			}
   239  
   240  			// Add the dummy forward to the forwards set
   241  			forwards.Add(forward)
   242  		}
   243  	}
   244  
   245  	if forwards.Len() > 0 {
   246  		d.Set("forward", forwards)
   247  	} else if !managed {
   248  		d.SetId("")
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error {
   255  	// Check if the forward set as a whole has changed
   256  	if d.HasChange("forward") {
   257  		o, n := d.GetChange("forward")
   258  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   259  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   260  
   261  		// Now first loop through all the old forwards and delete any obsolete ones
   262  		for _, forward := range ors.List() {
   263  			// Delete the forward as it no longer exists in the config
   264  			err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
   265  			if err != nil {
   266  				return err
   267  			}
   268  		}
   269  
   270  		// Make sure we save the state of the currently configured forwards
   271  		forwards := o.(*schema.Set).Intersection(n.(*schema.Set))
   272  		d.Set("forward", forwards)
   273  
   274  		// Then loop through all the currently configured forwards and create the new ones
   275  		for _, forward := range nrs.List() {
   276  			err := resourceCloudStackPortForwardCreateForward(
   277  				d, meta, forward.(map[string]interface{}))
   278  
   279  			// We need to update this first to preserve the correct state
   280  			forwards.Add(forward)
   281  			d.Set("forward", forwards)
   282  
   283  			if err != nil {
   284  				return err
   285  			}
   286  		}
   287  	}
   288  
   289  	return resourceCloudStackPortForwardRead(d, meta)
   290  }
   291  
   292  func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error {
   293  	// Delete all forwards
   294  	if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 {
   295  		for _, forward := range rs.List() {
   296  			// Delete a single forward
   297  			err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{}))
   298  
   299  			// We need to update this first to preserve the correct state
   300  			d.Set("forward", rs)
   301  
   302  			if err != nil {
   303  				return err
   304  			}
   305  		}
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  func resourceCloudStackPortForwardDeleteForward(
   312  	d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error {
   313  	cs := meta.(*cloudstack.CloudStackClient)
   314  
   315  	// Create the parameter struct
   316  	p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string))
   317  
   318  	// Delete the forward
   319  	if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil {
   320  		// This is a very poor way to be told the ID does no longer exist :(
   321  		if !strings.Contains(err.Error(), fmt.Sprintf(
   322  			"Invalid parameter id value=%s due to incorrect long value format, "+
   323  				"or entity does not exist", forward["uuid"].(string))) {
   324  			return err
   325  		}
   326  	}
   327  
   328  	// Empty the UUID of this rule
   329  	forward["uuid"] = ""
   330  
   331  	return nil
   332  }
   333  
   334  func resourceCloudStackPortForwardHash(v interface{}) int {
   335  	var buf bytes.Buffer
   336  	m := v.(map[string]interface{})
   337  	buf.WriteString(fmt.Sprintf(
   338  		"%s-%d-%d-%s",
   339  		m["protocol"].(string),
   340  		m["private_port"].(int),
   341  		m["public_port"].(int),
   342  		m["virtual_machine"].(string)))
   343  
   344  	return hashcode.String(buf.String())
   345  }
   346  
   347  func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error {
   348  	protocol := forward["protocol"].(string)
   349  	if protocol != "tcp" && protocol != "udp" {
   350  		return fmt.Errorf(
   351  			"%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol)
   352  	}
   353  	return nil
   354  }