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