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