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