github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go (about) 1 package cloudstack 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/xanzy/go-cloudstack/cloudstack" 13 ) 14 15 func resourceCloudStackEgressFirewall() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceCloudStackEgressFirewallCreate, 18 Read: resourceCloudStackEgressFirewallRead, 19 Update: resourceCloudStackEgressFirewallUpdate, 20 Delete: resourceCloudStackEgressFirewallDelete, 21 22 Schema: map[string]*schema.Schema{ 23 "network": &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 "rule": &schema.Schema{ 36 Type: schema.TypeSet, 37 Optional: true, 38 Elem: &schema.Resource{ 39 Schema: map[string]*schema.Schema{ 40 "cidr_list": &schema.Schema{ 41 Type: schema.TypeSet, 42 Optional: true, 43 Elem: &schema.Schema{Type: schema.TypeString}, 44 Set: schema.HashString, 45 }, 46 47 "source_cidr": &schema.Schema{ 48 Type: schema.TypeString, 49 Optional: true, 50 Deprecated: "Please use the `cidr_list` field instead", 51 }, 52 53 "protocol": &schema.Schema{ 54 Type: schema.TypeString, 55 Required: true, 56 }, 57 58 "icmp_type": &schema.Schema{ 59 Type: schema.TypeInt, 60 Optional: true, 61 Computed: true, 62 }, 63 64 "icmp_code": &schema.Schema{ 65 Type: schema.TypeInt, 66 Optional: true, 67 Computed: true, 68 }, 69 70 "ports": &schema.Schema{ 71 Type: schema.TypeSet, 72 Optional: true, 73 Elem: &schema.Schema{Type: schema.TypeString}, 74 Set: schema.HashString, 75 }, 76 77 "uuids": &schema.Schema{ 78 Type: schema.TypeMap, 79 Computed: true, 80 }, 81 }, 82 }, 83 }, 84 85 "parallelism": &schema.Schema{ 86 Type: schema.TypeInt, 87 Optional: true, 88 Default: 2, 89 }, 90 }, 91 } 92 } 93 94 func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error { 95 cs := meta.(*cloudstack.CloudStackClient) 96 97 // Make sure all required parameters are there 98 if err := verifyEgressFirewallParams(d); err != nil { 99 return err 100 } 101 102 // Retrieve the network ID 103 networkid, e := retrieveID(cs, "network", d.Get("network").(string)) 104 if e != nil { 105 return e.Error() 106 } 107 108 // We need to set this upfront in order to be able to save a partial state 109 d.SetId(networkid) 110 111 // Create all rules that are configured 112 if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 { 113 // Create an empty schema.Set to hold all rules 114 rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set) 115 116 err := createEgressFirewallRules(d, meta, rules, nrs) 117 118 // We need to update this first to preserve the correct state 119 d.Set("rule", rules) 120 121 if err != nil { 122 return err 123 } 124 } 125 126 return resourceCloudStackEgressFirewallRead(d, meta) 127 } 128 129 func createEgressFirewallRules( 130 d *schema.ResourceData, 131 meta interface{}, 132 rules *schema.Set, 133 nrs *schema.Set) error { 134 var errs *multierror.Error 135 136 var wg sync.WaitGroup 137 wg.Add(nrs.Len()) 138 139 sem := make(chan struct{}, d.Get("parallelism").(int)) 140 for _, rule := range nrs.List() { 141 // Put in a tiny sleep here to avoid DoS'ing the API 142 time.Sleep(500 * time.Millisecond) 143 144 go func(rule map[string]interface{}) { 145 defer wg.Done() 146 sem <- struct{}{} 147 148 // Create a single rule 149 err := createEgressFirewallRule(d, meta, rule) 150 151 // If we have at least one UUID, we need to save the rule 152 if len(rule["uuids"].(map[string]interface{})) > 0 { 153 rules.Add(rule) 154 } 155 156 if err != nil { 157 errs = multierror.Append(errs, err) 158 } 159 160 <-sem 161 }(rule.(map[string]interface{})) 162 } 163 164 wg.Wait() 165 166 return errs.ErrorOrNil() 167 } 168 func createEgressFirewallRule( 169 d *schema.ResourceData, 170 meta interface{}, 171 rule map[string]interface{}) error { 172 cs := meta.(*cloudstack.CloudStackClient) 173 uuids := rule["uuids"].(map[string]interface{}) 174 175 // Make sure all required rule parameters are there 176 if err := verifyEgressFirewallRuleParams(d, rule); err != nil { 177 return err 178 } 179 180 // Create a new parameter struct 181 p := cs.Firewall.NewCreateEgressFirewallRuleParams(d.Id(), rule["protocol"].(string)) 182 183 // Set the CIDR list 184 p.SetCidrlist(retrieveCidrList(rule)) 185 186 // If the protocol is ICMP set the needed ICMP parameters 187 if rule["protocol"].(string) == "icmp" { 188 p.SetIcmptype(rule["icmp_type"].(int)) 189 p.SetIcmpcode(rule["icmp_code"].(int)) 190 191 r, err := cs.Firewall.CreateEgressFirewallRule(p) 192 if err != nil { 193 return err 194 } 195 uuids["icmp"] = r.Id 196 rule["uuids"] = uuids 197 } 198 199 // If protocol is not ICMP, loop through all ports 200 if rule["protocol"].(string) != "icmp" { 201 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 202 203 // Create an empty schema.Set to hold all processed ports 204 ports := &schema.Set{F: schema.HashString} 205 206 for _, port := range ps.List() { 207 if _, ok := uuids[port.(string)]; ok { 208 ports.Add(port) 209 rule["ports"] = ports 210 continue 211 } 212 213 m := splitPorts.FindStringSubmatch(port.(string)) 214 215 startPort, err := strconv.Atoi(m[1]) 216 if err != nil { 217 return err 218 } 219 220 endPort := startPort 221 if m[2] != "" { 222 endPort, err = strconv.Atoi(m[2]) 223 if err != nil { 224 return err 225 } 226 } 227 228 p.SetStartport(startPort) 229 p.SetEndport(endPort) 230 231 r, err := cs.Firewall.CreateEgressFirewallRule(p) 232 if err != nil { 233 return err 234 } 235 236 ports.Add(port) 237 rule["ports"] = ports 238 239 uuids[port.(string)] = r.Id 240 rule["uuids"] = uuids 241 } 242 } 243 } 244 245 return nil 246 } 247 248 func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error { 249 cs := meta.(*cloudstack.CloudStackClient) 250 251 // Get all the rules from the running environment 252 p := cs.Firewall.NewListEgressFirewallRulesParams() 253 p.SetNetworkid(d.Id()) 254 p.SetListall(true) 255 256 l, err := cs.Firewall.ListEgressFirewallRules(p) 257 if err != nil { 258 return err 259 } 260 261 // Make a map of all the rules so we can easily find a rule 262 ruleMap := make(map[string]*cloudstack.EgressFirewallRule, l.Count) 263 for _, r := range l.EgressFirewallRules { 264 ruleMap[r.Id] = r 265 } 266 267 // Create an empty schema.Set to hold all rules 268 rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set) 269 270 // Read all rules that are configured 271 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 272 for _, rule := range rs.List() { 273 rule := rule.(map[string]interface{}) 274 uuids := rule["uuids"].(map[string]interface{}) 275 276 if rule["protocol"].(string) == "icmp" { 277 id, ok := uuids["icmp"] 278 if !ok { 279 continue 280 } 281 282 // Get the rule 283 r, ok := ruleMap[id.(string)] 284 if !ok { 285 delete(uuids, "icmp") 286 continue 287 } 288 289 // Delete the known rule so only unknown rules remain in the ruleMap 290 delete(ruleMap, id.(string)) 291 292 // Update the values 293 rule["protocol"] = r.Protocol 294 rule["icmp_type"] = r.Icmptype 295 rule["icmp_code"] = r.Icmpcode 296 setCidrList(rule, r.Cidrlist) 297 rules.Add(rule) 298 } 299 300 // If protocol is not ICMP, loop through all ports 301 if rule["protocol"].(string) != "icmp" { 302 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 303 304 // Create an empty schema.Set to hold all ports 305 ports := &schema.Set{F: schema.HashString} 306 307 // Loop through all ports and retrieve their info 308 for _, port := range ps.List() { 309 id, ok := uuids[port.(string)] 310 if !ok { 311 continue 312 } 313 314 // Get the rule 315 r, ok := ruleMap[id.(string)] 316 if !ok { 317 delete(uuids, port.(string)) 318 continue 319 } 320 321 // Delete the known rule so only unknown rules remain in the ruleMap 322 delete(ruleMap, id.(string)) 323 324 // Update the values 325 rule["protocol"] = r.Protocol 326 setCidrList(rule, r.Cidrlist) 327 ports.Add(port) 328 } 329 330 // If there is at least one port found, add this rule to the rules set 331 if ports.Len() > 0 { 332 rule["ports"] = ports 333 rules.Add(rule) 334 } 335 } 336 } 337 } 338 } 339 340 // If this is a managed firewall, add all unknown rules into a single dummy rule 341 managed := d.Get("managed").(bool) 342 if managed && len(ruleMap) > 0 { 343 for uuid := range ruleMap { 344 // We need to create and add a dummy value to a schema.Set as the 345 // cidr_list is a required field and thus needs a value 346 cidrs := &schema.Set{F: schema.HashString} 347 cidrs.Add(uuid) 348 349 // Make a dummy rule to hold the unknown UUID 350 rule := map[string]interface{}{ 351 "cidr_list": uuid, 352 "protocol": uuid, 353 "uuids": map[string]interface{}{uuid: uuid}, 354 } 355 356 // Add the dummy rule to the rules set 357 rules.Add(rule) 358 } 359 } 360 361 if rules.Len() > 0 { 362 d.Set("rule", rules) 363 } else if !managed { 364 d.SetId("") 365 } 366 367 return nil 368 } 369 370 func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error { 371 // Make sure all required parameters are there 372 if err := verifyEgressFirewallParams(d); err != nil { 373 return err 374 } 375 376 // Check if the rule set as a whole has changed 377 if d.HasChange("rule") { 378 o, n := d.GetChange("rule") 379 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 380 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 381 382 // We need to start with a rule set containing all the rules we 383 // already have and want to keep. Any rules that are not deleted 384 // correctly and any newly created rules, will be added to this 385 // set to make sure we end up in a consistent state 386 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 387 388 // First loop through all the old rules and delete them 389 if ors.Len() > 0 { 390 err := deleteEgressFirewallRules(d, meta, rules, ors) 391 392 // We need to update this first to preserve the correct state 393 d.Set("rule", rules) 394 395 if err != nil { 396 return err 397 } 398 } 399 400 // Then loop through all the new rules and create them 401 if nrs.Len() > 0 { 402 err := createEgressFirewallRules(d, meta, rules, nrs) 403 404 // We need to update this first to preserve the correct state 405 d.Set("rule", rules) 406 407 if err != nil { 408 return err 409 } 410 } 411 } 412 413 return resourceCloudStackEgressFirewallRead(d, meta) 414 } 415 416 func resourceCloudStackEgressFirewallDelete(d *schema.ResourceData, meta interface{}) error { 417 // Create an empty rule set to hold all rules that where 418 // not deleted correctly 419 rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set) 420 421 // Delete all rules 422 if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 { 423 err := deleteEgressFirewallRules(d, meta, rules, ors) 424 425 // We need to update this first to preserve the correct state 426 d.Set("rule", rules) 427 428 if err != nil { 429 return err 430 } 431 } 432 433 return nil 434 } 435 436 func deleteEgressFirewallRules( 437 d *schema.ResourceData, 438 meta interface{}, 439 rules *schema.Set, 440 ors *schema.Set) error { 441 var errs *multierror.Error 442 443 var wg sync.WaitGroup 444 wg.Add(ors.Len()) 445 446 sem := make(chan struct{}, d.Get("parallelism").(int)) 447 for _, rule := range ors.List() { 448 // Put a sleep here to avoid DoS'ing the API 449 time.Sleep(500 * time.Millisecond) 450 451 go func(rule map[string]interface{}) { 452 defer wg.Done() 453 sem <- struct{}{} 454 455 // Delete a single rule 456 err := deleteEgressFirewallRule(d, meta, rule) 457 458 // If we have at least one UUID, we need to save the rule 459 if len(rule["uuids"].(map[string]interface{})) > 0 { 460 rules.Add(rule) 461 } 462 463 if err != nil { 464 errs = multierror.Append(errs, err) 465 } 466 467 <-sem 468 }(rule.(map[string]interface{})) 469 } 470 471 wg.Wait() 472 473 return errs.ErrorOrNil() 474 } 475 476 func deleteEgressFirewallRule( 477 d *schema.ResourceData, 478 meta interface{}, 479 rule map[string]interface{}) error { 480 cs := meta.(*cloudstack.CloudStackClient) 481 uuids := rule["uuids"].(map[string]interface{}) 482 483 for k, id := range uuids { 484 // We don't care about the count here, so just continue 485 if k == "#" { 486 continue 487 } 488 489 // Create the parameter struct 490 p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string)) 491 492 // Delete the rule 493 if _, err := cs.Firewall.DeleteEgressFirewallRule(p); err != nil { 494 495 // This is a very poor way to be told the ID does no longer exist :( 496 if strings.Contains(err.Error(), fmt.Sprintf( 497 "Invalid parameter id value=%s due to incorrect long value format, "+ 498 "or entity does not exist", id.(string))) { 499 delete(uuids, k) 500 continue 501 } 502 503 return err 504 } 505 506 // Delete the UUID of this rule 507 delete(uuids, k) 508 rule["uuids"] = uuids 509 } 510 511 return nil 512 } 513 514 func verifyEgressFirewallParams(d *schema.ResourceData) error { 515 managed := d.Get("managed").(bool) 516 _, rules := d.GetOk("rule") 517 518 if !rules && !managed { 519 return fmt.Errorf( 520 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 521 } 522 523 return nil 524 } 525 526 func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 527 cidrList := rule["cidr_list"].(*schema.Set) 528 sourceCidr := rule["source_cidr"].(string) 529 if cidrList.Len() == 0 && sourceCidr == "" { 530 return fmt.Errorf( 531 "Parameter cidr_list is a required parameter") 532 } 533 if cidrList.Len() > 0 && sourceCidr != "" { 534 return fmt.Errorf( 535 "Parameter source_cidr is deprecated and cannot be used together with cidr_list") 536 } 537 538 protocol := rule["protocol"].(string) 539 if protocol != "tcp" && protocol != "udp" && protocol != "icmp" { 540 return fmt.Errorf( 541 "%q is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol) 542 } 543 544 if protocol == "icmp" { 545 if _, ok := rule["icmp_type"]; !ok { 546 return fmt.Errorf( 547 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 548 } 549 if _, ok := rule["icmp_code"]; !ok { 550 return fmt.Errorf( 551 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 552 } 553 } else { 554 if ports, ok := rule["ports"].(*schema.Set); ok { 555 for _, port := range ports.List() { 556 m := splitPorts.FindStringSubmatch(port.(string)) 557 if m == nil { 558 return fmt.Errorf( 559 "%q is not a valid port value. Valid options are '80' or '80-90'", port.(string)) 560 } 561 } 562 } else { 563 return fmt.Errorf( 564 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 565 } 566 } 567 568 return nil 569 }