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