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