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