github.com/econnell/terraform@v0.5.4-0.20150722160631-78eb236786a4/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 virtualmachineid, e := retrieveUUID(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 UUID 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 isUUID(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 228 for _, id := range forward["uuids"].(map[string]interface{}) { 229 delete(uuids, id.(string)) 230 } 231 } 232 233 for uuid := range uuids { 234 // Make a dummy forward to hold the unknown UUID 235 forward := map[string]interface{}{ 236 "protocol": "N/A", 237 "private_port": 0, 238 "public_port": 0, 239 "virtual_machine": uuid, 240 "uuid": uuid, 241 } 242 243 // Add the dummy forward to the forwards set 244 forwards.Add(forward) 245 } 246 } 247 248 if forwards.Len() > 0 { 249 d.Set("forward", forwards) 250 } else if !managed { 251 d.SetId("") 252 } 253 254 return nil 255 } 256 257 func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error { 258 // Check if the forward set as a whole has changed 259 if d.HasChange("forward") { 260 o, n := d.GetChange("forward") 261 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 262 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 263 264 // Now first loop through all the old forwards and delete any obsolete ones 265 for _, forward := range ors.List() { 266 // Delete the forward as it no longer exists in the config 267 err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{})) 268 if err != nil { 269 return err 270 } 271 } 272 273 // Make sure we save the state of the currently configured forwards 274 forwards := o.(*schema.Set).Intersection(n.(*schema.Set)) 275 d.Set("forward", forwards) 276 277 // Then loop through all the currently configured forwards and create the new ones 278 for _, forward := range nrs.List() { 279 err := resourceCloudStackPortForwardCreateForward( 280 d, meta, forward.(map[string]interface{})) 281 282 // We need to update this first to preserve the correct state 283 forwards.Add(forward) 284 d.Set("forward", forwards) 285 286 if err != nil { 287 return err 288 } 289 } 290 } 291 292 return resourceCloudStackPortForwardRead(d, meta) 293 } 294 295 func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error { 296 // Delete all forwards 297 if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 { 298 for _, forward := range rs.List() { 299 // Delete a single forward 300 err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{})) 301 302 // We need to update this first to preserve the correct state 303 d.Set("forward", rs) 304 305 if err != nil { 306 return err 307 } 308 } 309 } 310 311 return nil 312 } 313 314 func resourceCloudStackPortForwardDeleteForward( 315 d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error { 316 cs := meta.(*cloudstack.CloudStackClient) 317 318 // Create the parameter struct 319 p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string)) 320 321 // Delete the forward 322 if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil { 323 // This is a very poor way to be told the UUID does no longer exist :( 324 if !strings.Contains(err.Error(), fmt.Sprintf( 325 "Invalid parameter id value=%s due to incorrect long value format, "+ 326 "or entity does not exist", forward["uuid"].(string))) { 327 return err 328 } 329 } 330 331 forward["uuid"] = "" 332 333 return nil 334 } 335 336 func resourceCloudStackPortForwardHash(v interface{}) int { 337 var buf bytes.Buffer 338 m := v.(map[string]interface{}) 339 buf.WriteString(fmt.Sprintf( 340 "%s-%d-%d-%s", 341 m["protocol"].(string), 342 m["private_port"].(int), 343 m["public_port"].(int), 344 m["virtual_machine"].(string))) 345 346 return hashcode.String(buf.String()) 347 } 348 349 func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error { 350 protocol := forward["protocol"].(string) 351 if protocol != "tcp" && protocol != "udp" { 352 return fmt.Errorf( 353 "%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol) 354 } 355 return nil 356 }