github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go (about) 1 package cloudstack 2 3 import ( 4 "bytes" 5 "fmt" 6 "regexp" 7 "sort" 8 "strconv" 9 "strings" 10 11 "github.com/hashicorp/terraform/helper/hashcode" 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 "aclid": &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 "source_cidr": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: true, 50 }, 51 52 "protocol": &schema.Schema{ 53 Type: schema.TypeString, 54 Required: true, 55 }, 56 57 "icmp_type": &schema.Schema{ 58 Type: schema.TypeInt, 59 Optional: true, 60 Computed: true, 61 }, 62 63 "icmp_code": &schema.Schema{ 64 Type: schema.TypeInt, 65 Optional: true, 66 Computed: true, 67 }, 68 69 "ports": &schema.Schema{ 70 Type: schema.TypeSet, 71 Optional: true, 72 Elem: &schema.Schema{Type: schema.TypeString}, 73 Set: func(v interface{}) int { 74 return hashcode.String(v.(string)) 75 }, 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 Set: resourceCloudStackNetworkACLRuleHash, 91 }, 92 }, 93 } 94 } 95 96 func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error { 97 // Make sure all required parameters are there 98 if err := verifyNetworkACLParams(d); err != nil { 99 return err 100 } 101 102 // We need to set this upfront in order to be able to save a partial state 103 d.SetId(d.Get("aclid").(string)) 104 105 // Create all rules that are configured 106 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 107 108 // Create an empty schema.Set to hold all rules 109 rules := &schema.Set{ 110 F: resourceCloudStackNetworkACLRuleHash, 111 } 112 113 for _, rule := range rs.List() { 114 // Create a single rule 115 err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) 116 117 // We need to update this first to preserve the correct state 118 rules.Add(rule) 119 d.Set("rule", rules) 120 121 if err != nil { 122 return err 123 } 124 } 125 } 126 127 return resourceCloudStackNetworkACLRuleRead(d, meta) 128 } 129 130 func resourceCloudStackNetworkACLRuleCreateRule( 131 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 132 cs := meta.(*cloudstack.CloudStackClient) 133 uuids := rule["uuids"].(map[string]interface{}) 134 135 // Make sure all required parameters are there 136 if err := verifyNetworkACLRuleParams(d, rule); err != nil { 137 return err 138 } 139 140 // Create a new parameter struct 141 p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string)) 142 143 // Set the acl ID 144 p.SetAclid(d.Id()) 145 146 // Set the action 147 p.SetAction(rule["action"].(string)) 148 149 // Set the CIDR list 150 p.SetCidrlist([]string{rule["source_cidr"].(string)}) 151 152 // Set the traffic type 153 p.SetTraffictype(rule["traffic_type"].(string)) 154 155 // If the protocol is ICMP set the needed ICMP parameters 156 if rule["protocol"].(string) == "icmp" { 157 p.SetIcmptype(rule["icmp_type"].(int)) 158 p.SetIcmpcode(rule["icmp_code"].(int)) 159 160 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 161 if err != nil { 162 return err 163 } 164 165 uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id 166 rule["uuids"] = uuids 167 } 168 169 // If the protocol is ALL set the needed parameters 170 if rule["protocol"].(string) == "all" { 171 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 172 if err != nil { 173 return err 174 } 175 176 uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id 177 rule["uuids"] = uuids 178 } 179 180 // If protocol is TCP or UDP, loop through all ports 181 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 182 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 183 184 // Create an empty schema.Set to hold all processed ports 185 ports := &schema.Set{ 186 F: func(v interface{}) int { 187 return hashcode.String(v.(string)) 188 }, 189 } 190 191 for _, port := range ps.List() { 192 re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`) 193 m := re.FindStringSubmatch(port.(string)) 194 195 startPort, err := strconv.Atoi(m[1]) 196 if err != nil { 197 return err 198 } 199 200 endPort := startPort 201 if m[2] != "" { 202 endPort, err = strconv.Atoi(m[2]) 203 if err != nil { 204 return err 205 } 206 } 207 208 p.SetStartport(startPort) 209 p.SetEndport(endPort) 210 211 r, err := Retry(4, retryableACLCreationFunc(cs, p)) 212 if err != nil { 213 return err 214 } 215 216 ports.Add(port) 217 rule["ports"] = ports 218 219 uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id 220 rule["uuids"] = uuids 221 } 222 } 223 } 224 225 return nil 226 } 227 228 func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error { 229 cs := meta.(*cloudstack.CloudStackClient) 230 231 // Create an empty schema.Set to hold all rules 232 rules := &schema.Set{ 233 F: resourceCloudStackNetworkACLRuleHash, 234 } 235 236 // Read all rules that are configured 237 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 238 for _, rule := range rs.List() { 239 rule := rule.(map[string]interface{}) 240 uuids := rule["uuids"].(map[string]interface{}) 241 242 if rule["protocol"].(string) == "icmp" { 243 id, ok := uuids["icmp"] 244 if !ok { 245 continue 246 } 247 248 // Get the rule 249 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 250 // If the count == 0, there is no object found for this UUID 251 if err != nil { 252 if count == 0 { 253 delete(uuids, "icmp") 254 continue 255 } 256 257 return err 258 } 259 260 // Update the values 261 rule["action"] = strings.ToLower(r.Action) 262 rule["source_cidr"] = r.Cidrlist 263 rule["protocol"] = r.Protocol 264 rule["icmp_type"] = r.Icmptype 265 rule["icmp_code"] = r.Icmpcode 266 rule["traffic_type"] = strings.ToLower(r.Traffictype) 267 rules.Add(rule) 268 } 269 270 if rule["protocol"].(string) == "all" { 271 id, ok := uuids["all"] 272 if !ok { 273 continue 274 } 275 276 // Get the rule 277 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 278 // If the count == 0, there is no object found for this UUID 279 if err != nil { 280 if count == 0 { 281 delete(uuids, "all") 282 continue 283 } 284 285 return err 286 } 287 288 // Update the values 289 rule["action"] = strings.ToLower(r.Action) 290 rule["source_cidr"] = r.Cidrlist 291 rule["protocol"] = r.Protocol 292 rule["traffic_type"] = strings.ToLower(r.Traffictype) 293 rules.Add(rule) 294 } 295 296 // If protocol is tcp or udp, loop through all ports 297 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 298 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 299 300 // Create an empty schema.Set to hold all ports 301 ports := &schema.Set{ 302 F: func(v interface{}) int { 303 return hashcode.String(v.(string)) 304 }, 305 } 306 307 // Loop through all ports and retrieve their info 308 for _, port := range ps.List() { 309 id, ok := uuids[port.(string)] 310 if !ok { 311 continue 312 } 313 314 // Get the rule 315 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 316 if err != nil { 317 if count == 0 { 318 delete(uuids, port.(string)) 319 continue 320 } 321 322 return err 323 } 324 325 // Update the values 326 rule["action"] = strings.ToLower(r.Action) 327 rule["source_cidr"] = r.Cidrlist 328 rule["protocol"] = r.Protocol 329 rule["traffic_type"] = strings.ToLower(r.Traffictype) 330 ports.Add(port) 331 } 332 333 // If there is at least one port found, add this rule to the rules set 334 if ports.Len() > 0 { 335 rule["ports"] = ports 336 rules.Add(rule) 337 } 338 } 339 } 340 } 341 } 342 343 // If this is a managed firewall, add all unknown rules into a single dummy rule 344 managed := d.Get("managed").(bool) 345 if managed { 346 // Get all the rules from the running environment 347 p := cs.NetworkACL.NewListNetworkACLsParams() 348 p.SetAclid(d.Id()) 349 p.SetListall(true) 350 351 r, err := cs.NetworkACL.ListNetworkACLs(p) 352 if err != nil { 353 return err 354 } 355 356 // Add all UUIDs to the uuids map 357 uuids := make(map[string]interface{}, len(r.NetworkACLs)) 358 for _, r := range r.NetworkACLs { 359 uuids[r.Id] = r.Id 360 } 361 362 // Delete all expected UUIDs from the uuids map 363 for _, rule := range rules.List() { 364 rule := rule.(map[string]interface{}) 365 366 for _, id := range rule["uuids"].(map[string]interface{}) { 367 delete(uuids, id.(string)) 368 } 369 } 370 371 if len(uuids) > 0 { 372 // Make a dummy rule to hold all unknown UUIDs 373 rule := map[string]interface{}{ 374 "source_cidr": "N/A", 375 "protocol": "N/A", 376 "uuids": uuids, 377 } 378 379 // Add the dummy rule to the rules set 380 rules.Add(rule) 381 } 382 } 383 384 if rules.Len() > 0 { 385 d.Set("rule", rules) 386 } else if !managed { 387 d.SetId("") 388 } 389 390 return nil 391 } 392 393 func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { 394 // Make sure all required parameters are there 395 if err := verifyNetworkACLParams(d); err != nil { 396 return err 397 } 398 399 // Check if the rule set as a whole has changed 400 if d.HasChange("rule") { 401 o, n := d.GetChange("rule") 402 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 403 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 404 405 // Now first loop through all the old rules and delete any obsolete ones 406 for _, rule := range ors.List() { 407 // Delete the rule as it no longer exists in the config 408 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 409 if err != nil { 410 return err 411 } 412 } 413 414 // Make sure we save the state of the currently configured rules 415 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 416 d.Set("rule", rules) 417 418 // Then loop through all the currently configured rules and create the new ones 419 for _, rule := range nrs.List() { 420 // When succesfully deleted, re-create it again if it still exists 421 err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) 422 423 // We need to update this first to preserve the correct state 424 rules.Add(rule) 425 d.Set("rule", rules) 426 427 if err != nil { 428 return err 429 } 430 } 431 } 432 433 return resourceCloudStackNetworkACLRuleRead(d, meta) 434 } 435 436 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error { 437 // Delete all rules 438 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 439 for _, rule := range rs.List() { 440 // Delete a single rule 441 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 442 443 // We need to update this first to preserve the correct state 444 d.Set("rule", rs) 445 446 if err != nil { 447 return err 448 } 449 } 450 } 451 452 return nil 453 } 454 455 func resourceCloudStackNetworkACLRuleDeleteRule( 456 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 457 cs := meta.(*cloudstack.CloudStackClient) 458 uuids := rule["uuids"].(map[string]interface{}) 459 460 for k, id := range uuids { 461 // We don't care about the count here, so just continue 462 if k == "#" { 463 continue 464 } 465 466 // Create the parameter struct 467 p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string)) 468 469 // Delete the rule 470 if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil { 471 472 // This is a very poor way to be told the UUID does no longer exist :( 473 if strings.Contains(err.Error(), fmt.Sprintf( 474 "Invalid parameter id value=%s due to incorrect long value format, "+ 475 "or entity does not exist", id.(string))) { 476 delete(uuids, k) 477 continue 478 } 479 480 return err 481 } 482 483 // Delete the UUID of this rule 484 delete(uuids, k) 485 } 486 487 // Update the UUIDs 488 rule["uuids"] = uuids 489 490 return nil 491 } 492 493 func resourceCloudStackNetworkACLRuleHash(v interface{}) int { 494 var buf bytes.Buffer 495 m := v.(map[string]interface{}) 496 497 // This is a little ugly, but it's needed because these arguments have 498 // a default value that needs to be part of the string to hash 499 var action, trafficType string 500 if a, ok := m["action"]; ok { 501 action = a.(string) 502 } else { 503 action = "allow" 504 } 505 if t, ok := m["traffic_type"]; ok { 506 trafficType = t.(string) 507 } else { 508 trafficType = "ingress" 509 } 510 511 buf.WriteString(fmt.Sprintf( 512 "%s-%s-%s-%s-", 513 action, 514 m["source_cidr"].(string), 515 m["protocol"].(string), 516 trafficType)) 517 518 if v, ok := m["icmp_type"]; ok { 519 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 520 } 521 522 if v, ok := m["icmp_code"]; ok { 523 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 524 } 525 526 // We need to make sure to sort the strings below so that we always 527 // generate the same hash code no matter what is in the set. 528 if v, ok := m["ports"]; ok { 529 vs := v.(*schema.Set).List() 530 s := make([]string, len(vs)) 531 532 for i, raw := range vs { 533 s[i] = raw.(string) 534 } 535 sort.Strings(s) 536 537 for _, v := range s { 538 buf.WriteString(fmt.Sprintf("%s-", v)) 539 } 540 } 541 542 return hashcode.String(buf.String()) 543 } 544 545 func verifyNetworkACLParams(d *schema.ResourceData) error { 546 managed := d.Get("managed").(bool) 547 _, rules := d.GetOk("rule") 548 549 if !rules && !managed { 550 return fmt.Errorf( 551 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 552 } 553 554 return nil 555 } 556 557 func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 558 action := rule["action"].(string) 559 if action != "allow" && action != "deny" { 560 return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") 561 } 562 563 protocol := rule["protocol"].(string) 564 switch protocol { 565 case "icmp": 566 if _, ok := rule["icmp_type"]; !ok { 567 return fmt.Errorf( 568 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 569 } 570 if _, ok := rule["icmp_code"]; !ok { 571 return fmt.Errorf( 572 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 573 } 574 case "all": 575 // No additional test are needed, so just leave this empty... 576 case "tcp", "udp": 577 if _, ok := rule["ports"]; !ok { 578 return fmt.Errorf( 579 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 580 } 581 default: 582 _, err := strconv.ParseInt(protocol, 0, 0) 583 if err != nil { 584 return fmt.Errorf( 585 "%s is not a valid protocol. Valid options are 'tcp', 'udp', "+ 586 "'icmp', 'all' or a valid protocol number", protocol) 587 } 588 } 589 590 traffic := rule["traffic_type"].(string) 591 if traffic != "ingress" && traffic != "egress" { 592 return fmt.Errorf( 593 "Parameter traffic_type only accepts 'ingress' or 'egress' as values") 594 } 595 596 return nil 597 } 598 599 func retryableACLCreationFunc( 600 cs *cloudstack.CloudStackClient, 601 p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) { 602 return func() (interface{}, error) { 603 r, err := cs.NetworkACL.CreateNetworkACL(p) 604 if err != nil { 605 return nil, err 606 } 607 return r, nil 608 } 609 }