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 }