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 }