github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.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 resourceCloudStackNetworkACLRule() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceCloudStackNetworkACLRuleCreate, 18 Read: resourceCloudStackNetworkACLRuleRead, 19 Update: resourceCloudStackNetworkACLRuleUpdate, 20 Delete: resourceCloudStackNetworkACLRuleDelete, 21 22 Schema: map[string]*schema.Schema{ 23 "acl_id": &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 "action": &schema.Schema{ 41 Type: schema.TypeString, 42 Optional: true, 43 Default: "allow", 44 }, 45 46 "cidr_list": &schema.Schema{ 47 Type: schema.TypeSet, 48 Required: true, 49 Elem: &schema.Schema{Type: schema.TypeString}, 50 Set: schema.HashString, 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 "traffic_type": &schema.Schema{ 78 Type: schema.TypeString, 79 Optional: true, 80 Default: "ingress", 81 }, 82 83 "uuids": &schema.Schema{ 84 Type: schema.TypeMap, 85 Computed: true, 86 }, 87 }, 88 }, 89 }, 90 91 "parallelism": &schema.Schema{ 92 Type: schema.TypeInt, 93 Optional: true, 94 Default: 2, 95 }, 96 }, 97 } 98 } 99 100 func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error { 101 // Make sure all required parameters are there 102 if err := verifyNetworkACLParams(d); err != nil { 103 return err 104 } 105 106 // We need to set this upfront in order to be able to save a partial state 107 d.SetId(d.Get("acl_id").(string)) 108 109 // Create all rules that are configured 110 if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 { 111 // Create an empty rule set to hold all newly created rules 112 rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set) 113 114 err := createNetworkACLRules(d, meta, rules, nrs) 115 116 // We need to update this first to preserve the correct state 117 d.Set("rule", rules) 118 119 if err != nil { 120 return err 121 } 122 } 123 124 return resourceCloudStackNetworkACLRuleRead(d, meta) 125 } 126 127 func createNetworkACLRules(d *schema.ResourceData, meta interface{}, rules *schema.Set, nrs *schema.Set) error { 128 var errs *multierror.Error 129 130 var wg sync.WaitGroup 131 wg.Add(nrs.Len()) 132 133 sem := make(chan struct{}, d.Get("parallelism").(int)) 134 for _, rule := range nrs.List() { 135 // Put in a tiny sleep here to avoid DoS'ing the API 136 time.Sleep(500 * time.Millisecond) 137 138 go func(rule map[string]interface{}) { 139 defer wg.Done() 140 sem <- struct{}{} 141 142 // Create a single rule 143 err := createNetworkACLRule(d, meta, rule) 144 145 // If we have at least one UUID, we need to save the rule 146 if len(rule["uuids"].(map[string]interface{})) > 0 { 147 rules.Add(rule) 148 } 149 150 if err != nil { 151 errs = multierror.Append(errs, err) 152 } 153 154 <-sem 155 }(rule.(map[string]interface{})) 156 } 157 158 wg.Wait() 159 160 return errs.ErrorOrNil() 161 } 162 163 func createNetworkACLRule(d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 164 cs := meta.(*cloudstack.CloudStackClient) 165 uuids := rule["uuids"].(map[string]interface{}) 166 167 // Make sure all required parameters are there 168 if err := verifyNetworkACLRuleParams(d, rule); err != nil { 169 return err 170 } 171 172 // Create a new parameter struct 173 p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string)) 174 175 // Set the acl ID 176 p.SetAclid(d.Id()) 177 178 // Set the action 179 p.SetAction(rule["action"].(string)) 180 181 // Set the CIDR list 182 var cidrList []string 183 for _, cidr := range rule["cidr_list"].(*schema.Set).List() { 184 cidrList = append(cidrList, cidr.(string)) 185 } 186 p.SetCidrlist(cidrList) 187 188 // Set the traffic type 189 p.SetTraffictype(rule["traffic_type"].(string)) 190 191 // If the protocol is ICMP set the needed ICMP parameters 192 if rule["protocol"].(string) == "icmp" { 193 p.SetIcmptype(rule["icmp_type"].(int)) 194 p.SetIcmpcode(rule["icmp_code"].(int)) 195 196 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 197 if err != nil { 198 return err 199 } 200 201 uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id 202 rule["uuids"] = uuids 203 } 204 205 // If the protocol is ALL set the needed parameters 206 if rule["protocol"].(string) == "all" { 207 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 208 if err != nil { 209 return err 210 } 211 212 uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id 213 rule["uuids"] = uuids 214 } 215 216 // If protocol is TCP or UDP, loop through all ports 217 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 218 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 219 220 // Create an empty schema.Set to hold all processed ports 221 ports := &schema.Set{F: schema.HashString} 222 223 for _, port := range ps.List() { 224 if _, ok := uuids[port.(string)]; ok { 225 ports.Add(port) 226 rule["ports"] = ports 227 continue 228 } 229 230 m := splitPorts.FindStringSubmatch(port.(string)) 231 232 startPort, err := strconv.Atoi(m[1]) 233 if err != nil { 234 return err 235 } 236 237 endPort := startPort 238 if m[2] != "" { 239 endPort, err = strconv.Atoi(m[2]) 240 if err != nil { 241 return err 242 } 243 } 244 245 p.SetStartport(startPort) 246 p.SetEndport(endPort) 247 248 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 249 if err != nil { 250 return err 251 } 252 253 ports.Add(port) 254 rule["ports"] = ports 255 256 uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id 257 rule["uuids"] = uuids 258 } 259 } 260 } 261 262 return nil 263 } 264 265 func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error { 266 cs := meta.(*cloudstack.CloudStackClient) 267 268 // Get all the rules from the running environment 269 p := cs.NetworkACL.NewListNetworkACLsParams() 270 p.SetAclid(d.Id()) 271 p.SetListall(true) 272 273 l, err := cs.NetworkACL.ListNetworkACLs(p) 274 if err != nil { 275 return err 276 } 277 278 // Make a map of all the rules so we can easily find a rule 279 ruleMap := make(map[string]*cloudstack.NetworkACL, l.Count) 280 for _, r := range l.NetworkACLs { 281 ruleMap[r.Id] = r 282 } 283 284 // Create an empty schema.Set to hold all rules 285 rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set) 286 287 // Read all rules that are configured 288 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 289 for _, rule := range rs.List() { 290 rule := rule.(map[string]interface{}) 291 uuids := rule["uuids"].(map[string]interface{}) 292 293 if rule["protocol"].(string) == "icmp" { 294 id, ok := uuids["icmp"] 295 if !ok { 296 continue 297 } 298 299 // Get the rule 300 r, ok := ruleMap[id.(string)] 301 if !ok { 302 delete(uuids, "icmp") 303 continue 304 } 305 306 // Delete the known rule so only unknown rules remain in the ruleMap 307 delete(ruleMap, id.(string)) 308 309 // Create a set with all CIDR's 310 cidrs := &schema.Set{F: schema.HashString} 311 for _, cidr := range strings.Split(r.Cidrlist, ",") { 312 cidrs.Add(cidr) 313 } 314 315 // Update the values 316 rule["action"] = strings.ToLower(r.Action) 317 rule["protocol"] = r.Protocol 318 rule["icmp_type"] = r.Icmptype 319 rule["icmp_code"] = r.Icmpcode 320 rule["traffic_type"] = strings.ToLower(r.Traffictype) 321 rule["cidr_list"] = cidrs 322 rules.Add(rule) 323 } 324 325 if rule["protocol"].(string) == "all" { 326 id, ok := uuids["all"] 327 if !ok { 328 continue 329 } 330 331 // Get the rule 332 r, ok := ruleMap[id.(string)] 333 if !ok { 334 delete(uuids, "all") 335 continue 336 } 337 338 // Delete the known rule so only unknown rules remain in the ruleMap 339 delete(ruleMap, id.(string)) 340 341 // Create a set with all CIDR's 342 cidrs := &schema.Set{F: schema.HashString} 343 for _, cidr := range strings.Split(r.Cidrlist, ",") { 344 cidrs.Add(cidr) 345 } 346 347 // Update the values 348 rule["action"] = strings.ToLower(r.Action) 349 rule["protocol"] = r.Protocol 350 rule["traffic_type"] = strings.ToLower(r.Traffictype) 351 rule["cidr_list"] = cidrs 352 rules.Add(rule) 353 } 354 355 // If protocol is tcp or udp, loop through all ports 356 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 357 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 358 359 // Create an empty schema.Set to hold all ports 360 ports := &schema.Set{F: schema.HashString} 361 362 // Loop through all ports and retrieve their info 363 for _, port := range ps.List() { 364 id, ok := uuids[port.(string)] 365 if !ok { 366 continue 367 } 368 369 // Get the rule 370 r, ok := ruleMap[id.(string)] 371 if !ok { 372 delete(uuids, port.(string)) 373 continue 374 } 375 376 // Delete the known rule so only unknown rules remain in the ruleMap 377 delete(ruleMap, id.(string)) 378 379 // Create a set with all CIDR's 380 cidrs := &schema.Set{F: schema.HashString} 381 for _, cidr := range strings.Split(r.Cidrlist, ",") { 382 cidrs.Add(cidr) 383 } 384 385 // Update the values 386 rule["action"] = strings.ToLower(r.Action) 387 rule["protocol"] = r.Protocol 388 rule["traffic_type"] = strings.ToLower(r.Traffictype) 389 rule["cidr_list"] = cidrs 390 ports.Add(port) 391 } 392 393 // If there is at least one port found, add this rule to the rules set 394 if ports.Len() > 0 { 395 rule["ports"] = ports 396 rules.Add(rule) 397 } 398 } 399 } 400 } 401 } 402 403 // If this is a managed firewall, add all unknown rules into dummy rules 404 managed := d.Get("managed").(bool) 405 if managed && len(ruleMap) > 0 { 406 for uuid := range ruleMap { 407 // We need to create and add a dummy value to a schema.Set as the 408 // cidr_list is a required field and thus needs a value 409 cidrs := &schema.Set{F: schema.HashString} 410 cidrs.Add(uuid) 411 412 // Make a dummy rule to hold the unknown UUID 413 rule := map[string]interface{}{ 414 "cidr_list": cidrs, 415 "protocol": uuid, 416 "uuids": map[string]interface{}{uuid: uuid}, 417 } 418 419 // Add the dummy rule to the rules set 420 rules.Add(rule) 421 } 422 } 423 424 if rules.Len() > 0 { 425 d.Set("rule", rules) 426 } else if !managed { 427 d.SetId("") 428 } 429 430 return nil 431 } 432 433 func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { 434 // Make sure all required parameters are there 435 if err := verifyNetworkACLParams(d); err != nil { 436 return err 437 } 438 439 // Check if the rule set as a whole has changed 440 if d.HasChange("rule") { 441 o, n := d.GetChange("rule") 442 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 443 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 444 445 // We need to start with a rule set containing all the rules we 446 // already have and want to keep. Any rules that are not deleted 447 // correctly and any newly created rules, will be added to this 448 // set to make sure we end up in a consistent state 449 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 450 451 // First loop through all the new rules and create (before destroy) them 452 if nrs.Len() > 0 { 453 err := createNetworkACLRules(d, meta, rules, nrs) 454 455 // We need to update this first to preserve the correct state 456 d.Set("rule", rules) 457 458 if err != nil { 459 return err 460 } 461 } 462 463 // Then loop through all the old rules and delete them 464 if ors.Len() > 0 { 465 err := deleteNetworkACLRules(d, meta, rules, ors) 466 467 // We need to update this first to preserve the correct state 468 d.Set("rule", rules) 469 470 if err != nil { 471 return err 472 } 473 } 474 } 475 476 return resourceCloudStackNetworkACLRuleRead(d, meta) 477 } 478 479 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error { 480 // Create an empty rule set to hold all rules that where 481 // not deleted correctly 482 rules := resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set) 483 484 // Delete all rules 485 if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 { 486 err := deleteNetworkACLRules(d, meta, rules, ors) 487 488 // We need to update this first to preserve the correct state 489 d.Set("rule", rules) 490 491 if err != nil { 492 return err 493 } 494 } 495 496 return nil 497 } 498 499 func deleteNetworkACLRules( 500 d *schema.ResourceData, 501 meta interface{}, 502 rules *schema.Set, 503 ors *schema.Set) error { 504 var errs *multierror.Error 505 506 var wg sync.WaitGroup 507 wg.Add(ors.Len()) 508 509 sem := make(chan struct{}, d.Get("parallelism").(int)) 510 for _, rule := range ors.List() { 511 // Put a sleep here to avoid DoS'ing the API 512 time.Sleep(500 * time.Millisecond) 513 514 go func(rule map[string]interface{}) { 515 defer wg.Done() 516 sem <- struct{}{} 517 518 // Delete a single rule 519 err := deleteNetworkACLRule(d, meta, rule) 520 521 // If we have at least one UUID, we need to save the rule 522 if len(rule["uuids"].(map[string]interface{})) > 0 { 523 rules.Add(rule) 524 } 525 526 if err != nil { 527 errs = multierror.Append(errs, err) 528 } 529 530 <-sem 531 }(rule.(map[string]interface{})) 532 } 533 534 wg.Wait() 535 536 return errs.ErrorOrNil() 537 } 538 539 func deleteNetworkACLRule( 540 d *schema.ResourceData, 541 meta interface{}, 542 rule map[string]interface{}) error { 543 cs := meta.(*cloudstack.CloudStackClient) 544 uuids := rule["uuids"].(map[string]interface{}) 545 546 for k, id := range uuids { 547 // We don't care about the count here, so just continue 548 if k == "%" { 549 continue 550 } 551 552 // Create the parameter struct 553 p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string)) 554 555 // Delete the rule 556 if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil { 557 558 // This is a very poor way to be told the ID does no longer exist :( 559 if strings.Contains(err.Error(), fmt.Sprintf( 560 "Invalid parameter id value=%s due to incorrect long value format, "+ 561 "or entity does not exist", id.(string))) { 562 delete(uuids, k) 563 rule["uuids"] = uuids 564 continue 565 } 566 567 return err 568 } 569 570 // Delete the UUID of this rule 571 delete(uuids, k) 572 rule["uuids"] = uuids 573 } 574 575 return nil 576 } 577 578 func verifyNetworkACLParams(d *schema.ResourceData) error { 579 managed := d.Get("managed").(bool) 580 _, rules := d.GetOk("rule") 581 582 if !rules && !managed { 583 return fmt.Errorf( 584 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 585 } 586 587 return nil 588 } 589 590 func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 591 action := rule["action"].(string) 592 if action != "allow" && action != "deny" { 593 return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") 594 } 595 596 protocol := rule["protocol"].(string) 597 switch protocol { 598 case "icmp": 599 if _, ok := rule["icmp_type"]; !ok { 600 return fmt.Errorf( 601 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 602 } 603 if _, ok := rule["icmp_code"]; !ok { 604 return fmt.Errorf( 605 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 606 } 607 case "all": 608 // No additional test are needed, so just leave this empty... 609 case "tcp", "udp": 610 if ports, ok := rule["ports"].(*schema.Set); ok { 611 for _, port := range ports.List() { 612 m := splitPorts.FindStringSubmatch(port.(string)) 613 if m == nil { 614 return fmt.Errorf( 615 "%q is not a valid port value. Valid options are '80' or '80-90'", port.(string)) 616 } 617 } 618 } else { 619 return fmt.Errorf( 620 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 621 } 622 default: 623 _, err := strconv.ParseInt(protocol, 0, 0) 624 if err != nil { 625 return fmt.Errorf( 626 "%q is not a valid protocol. Valid options are 'tcp', 'udp', "+ 627 "'icmp', 'all' or a valid protocol number", protocol) 628 } 629 } 630 631 traffic := rule["traffic_type"].(string) 632 if traffic != "ingress" && traffic != "egress" { 633 return fmt.Errorf( 634 "Parameter traffic_type only accepts 'ingress' or 'egress' as values") 635 } 636 637 return nil 638 } 639 640 func retryableACLCreationFunc( 641 cs *cloudstack.CloudStackClient, 642 p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) { 643 return func() (interface{}, error) { 644 r, err := cs.NetworkACL.CreateNetworkACL(p) 645 if err != nil { 646 return nil, err 647 } 648 return r, nil 649 } 650 }