github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/cloudstack/resource_cloudstack_port_forward.go (about) 1 package cloudstack 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 8 "strconv" 9 "strings" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/terraform/helper/schema" 13 "github.com/xanzy/go-cloudstack/cloudstack" 14 ) 15 16 func resourceCloudStackPortForward() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceCloudStackPortForwardCreate, 19 Read: resourceCloudStackPortForwardRead, 20 Update: resourceCloudStackPortForwardUpdate, 21 Delete: resourceCloudStackPortForwardDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "ipaddress": &schema.Schema{ 25 Type: schema.TypeString, 26 Required: true, 27 ForceNew: true, 28 }, 29 30 "managed": &schema.Schema{ 31 Type: schema.TypeBool, 32 Optional: true, 33 Default: false, 34 }, 35 36 "forward": &schema.Schema{ 37 Type: schema.TypeSet, 38 Required: true, 39 Elem: &schema.Resource{ 40 Schema: map[string]*schema.Schema{ 41 "protocol": &schema.Schema{ 42 Type: schema.TypeString, 43 Required: true, 44 }, 45 46 "private_port": &schema.Schema{ 47 Type: schema.TypeInt, 48 Required: true, 49 }, 50 51 "public_port": &schema.Schema{ 52 Type: schema.TypeInt, 53 Required: true, 54 }, 55 56 "virtual_machine": &schema.Schema{ 57 Type: schema.TypeString, 58 Required: true, 59 }, 60 61 "uuid": &schema.Schema{ 62 Type: schema.TypeString, 63 Computed: true, 64 }, 65 }, 66 }, 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 nrs := d.Get("forward").(*schema.Set); nrs.Len() > 0 { 86 // Create an empty schema.Set to hold all forwards 87 forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set) 88 89 err := createPortForwards(d, meta, forwards, nrs) 90 91 // We need to update this first to preserve the correct state 92 d.Set("forward", forwards) 93 94 if err != nil { 95 return err 96 } 97 } 98 99 return resourceCloudStackPortForwardRead(d, meta) 100 } 101 102 func createPortForwards( 103 d *schema.ResourceData, 104 meta interface{}, 105 forwards *schema.Set, 106 nrs *schema.Set) error { 107 var errs *multierror.Error 108 109 var wg sync.WaitGroup 110 wg.Add(nrs.Len()) 111 112 sem := make(chan struct{}, 10) 113 for _, forward := range nrs.List() { 114 // Put in a tiny sleep here to avoid DoS'ing the API 115 time.Sleep(500 * time.Millisecond) 116 117 go func(forward map[string]interface{}) { 118 defer wg.Done() 119 sem <- struct{}{} 120 121 // Create a single forward 122 err := createPortForward(d, meta, forward) 123 124 // If we have a UUID, we need to save the forward 125 if forward["uuid"].(string) != "" { 126 forwards.Add(forward) 127 } 128 129 if err != nil { 130 errs = multierror.Append(errs, err) 131 } 132 133 <-sem 134 }(forward.(map[string]interface{})) 135 } 136 137 wg.Wait() 138 139 return errs.ErrorOrNil() 140 } 141 func createPortForward( 142 d *schema.ResourceData, 143 meta interface{}, 144 forward map[string]interface{}) error { 145 cs := meta.(*cloudstack.CloudStackClient) 146 147 // Make sure all required parameters are there 148 if err := verifyPortForwardParams(d, forward); err != nil { 149 return err 150 } 151 152 // Retrieve the virtual_machine ID 153 virtualmachineid, e := retrieveID(cs, "virtual_machine", forward["virtual_machine"].(string)) 154 if e != nil { 155 return e.Error() 156 } 157 158 vm, _, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) 159 if err != nil { 160 return err 161 } 162 163 // Create a new parameter struct 164 p := cs.Firewall.NewCreatePortForwardingRuleParams(d.Id(), forward["private_port"].(int), 165 forward["protocol"].(string), forward["public_port"].(int), vm.Id) 166 167 // Set the network ID of the default network, needed when public IP address 168 // is not associated with any Guest network yet (VPC case) 169 p.SetNetworkid(vm.Nic[0].Networkid) 170 171 // Do not open the firewall automatically in any case 172 p.SetOpenfirewall(false) 173 174 r, err := cs.Firewall.CreatePortForwardingRule(p) 175 if err != nil { 176 return err 177 } 178 179 forward["uuid"] = r.Id 180 181 return nil 182 } 183 184 func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error { 185 cs := meta.(*cloudstack.CloudStackClient) 186 187 // Get all the forwards from the running environment 188 p := cs.Firewall.NewListPortForwardingRulesParams() 189 p.SetIpaddressid(d.Id()) 190 p.SetListall(true) 191 192 l, err := cs.Firewall.ListPortForwardingRules(p) 193 if err != nil { 194 return err 195 } 196 197 // Make a map of all the forwards so we can easily find a forward 198 forwardMap := make(map[string]*cloudstack.PortForwardingRule, l.Count) 199 for _, f := range l.PortForwardingRules { 200 forwardMap[f.Id] = f 201 } 202 203 // Create an empty schema.Set to hold all forwards 204 forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set) 205 206 // Read all forwards that are configured 207 if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 { 208 for _, forward := range rs.List() { 209 forward := forward.(map[string]interface{}) 210 211 id, ok := forward["uuid"] 212 if !ok || id.(string) == "" { 213 continue 214 } 215 216 // Get the forward 217 f, ok := forwardMap[id.(string)] 218 if !ok { 219 forward["uuid"] = "" 220 continue 221 } 222 223 // Delete the known rule so only unknown rules remain in the ruleMap 224 delete(forwardMap, id.(string)) 225 226 privPort, err := strconv.Atoi(f.Privateport) 227 if err != nil { 228 return err 229 } 230 231 pubPort, err := strconv.Atoi(f.Publicport) 232 if err != nil { 233 return err 234 } 235 236 // Update the values 237 forward["protocol"] = f.Protocol 238 forward["private_port"] = privPort 239 forward["public_port"] = pubPort 240 241 if isID(forward["virtual_machine"].(string)) { 242 forward["virtual_machine"] = f.Virtualmachineid 243 } else { 244 forward["virtual_machine"] = f.Virtualmachinename 245 } 246 247 forwards.Add(forward) 248 } 249 } 250 251 // If this is a managed resource, add all unknown forwards to dummy forwards 252 managed := d.Get("managed").(bool) 253 if managed && len(forwardMap) > 0 { 254 for uuid := range forwardMap { 255 // Make a dummy forward to hold the unknown UUID 256 forward := map[string]interface{}{ 257 "protocol": uuid, 258 "private_port": 0, 259 "public_port": 0, 260 "virtual_machine": uuid, 261 "uuid": uuid, 262 } 263 264 // Add the dummy forward to the forwards set 265 forwards.Add(forward) 266 } 267 } 268 269 if forwards.Len() > 0 { 270 d.Set("forward", forwards) 271 } else if !managed { 272 d.SetId("") 273 } 274 275 return nil 276 } 277 278 func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error { 279 // Check if the forward set as a whole has changed 280 if d.HasChange("forward") { 281 o, n := d.GetChange("forward") 282 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 283 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 284 285 // We need to start with a rule set containing all the rules we 286 // already have and want to keep. Any rules that are not deleted 287 // correctly and any newly created rules, will be added to this 288 // set to make sure we end up in a consistent state 289 forwards := o.(*schema.Set).Intersection(n.(*schema.Set)) 290 291 // First loop through all the new forwards and create (before destroy) them 292 if nrs.Len() > 0 { 293 err := createPortForwards(d, meta, forwards, nrs) 294 295 // We need to update this first to preserve the correct state 296 d.Set("forward", forwards) 297 298 if err != nil { 299 return err 300 } 301 } 302 303 // Then loop through all the old forwards and delete them 304 if ors.Len() > 0 { 305 err := deletePortForwards(d, meta, forwards, ors) 306 307 // We need to update this first to preserve the correct state 308 d.Set("forward", forwards) 309 310 if err != nil { 311 return err 312 } 313 } 314 } 315 316 return resourceCloudStackPortForwardRead(d, meta) 317 } 318 319 func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error { 320 // Create an empty rule set to hold all rules that where 321 // not deleted correctly 322 forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set) 323 324 // Delete all forwards 325 if ors := d.Get("forward").(*schema.Set); ors.Len() > 0 { 326 err := deletePortForwards(d, meta, forwards, ors) 327 328 // We need to update this first to preserve the correct state 329 d.Set("forward", forwards) 330 331 if err != nil { 332 return err 333 } 334 } 335 336 return nil 337 } 338 339 func deletePortForwards( 340 d *schema.ResourceData, 341 meta interface{}, 342 forwards *schema.Set, 343 ors *schema.Set) error { 344 var errs *multierror.Error 345 346 var wg sync.WaitGroup 347 wg.Add(ors.Len()) 348 349 sem := make(chan struct{}, 10) 350 for _, forward := range ors.List() { 351 // Put a sleep here to avoid DoS'ing the API 352 time.Sleep(500 * time.Millisecond) 353 354 go func(forward map[string]interface{}) { 355 defer wg.Done() 356 sem <- struct{}{} 357 358 // Delete a single forward 359 err := deletePortForward(d, meta, forward) 360 361 // If we have a UUID, we need to save the forward 362 if forward["uuid"].(string) != "" { 363 forwards.Add(forward) 364 } 365 366 if err != nil { 367 errs = multierror.Append(errs, err) 368 } 369 370 <-sem 371 }(forward.(map[string]interface{})) 372 } 373 374 wg.Wait() 375 376 return errs.ErrorOrNil() 377 } 378 379 func deletePortForward( 380 d *schema.ResourceData, 381 meta interface{}, 382 forward map[string]interface{}) error { 383 cs := meta.(*cloudstack.CloudStackClient) 384 385 // Create the parameter struct 386 p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string)) 387 388 // Delete the forward 389 if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil { 390 // This is a very poor way to be told the ID does no longer exist :( 391 if !strings.Contains(err.Error(), fmt.Sprintf( 392 "Invalid parameter id value=%s due to incorrect long value format, "+ 393 "or entity does not exist", forward["uuid"].(string))) { 394 return err 395 } 396 } 397 398 // Empty the UUID of this rule 399 forward["uuid"] = "" 400 401 return nil 402 } 403 404 func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error { 405 protocol := forward["protocol"].(string) 406 if protocol != "tcp" && protocol != "udp" { 407 return fmt.Errorf( 408 "%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol) 409 } 410 return nil 411 }