github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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 // Get all the rules from the running environment 232 p := cs.NetworkACL.NewListNetworkACLsParams() 233 p.SetAclid(d.Id()) 234 p.SetListall(true) 235 236 l, err := cs.NetworkACL.ListNetworkACLs(p) 237 if err != nil { 238 return err 239 } 240 241 // Make a map of all the rules so we can easily find a rule 242 ruleMap := make(map[string]*cloudstack.NetworkACL, l.Count) 243 for _, r := range l.NetworkACLs { 244 ruleMap[r.Id] = r 245 } 246 247 // Create an empty schema.Set to hold all rules 248 rules := &schema.Set{ 249 F: resourceCloudStackNetworkACLRuleHash, 250 } 251 252 // Read all rules that are configured 253 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 254 for _, rule := range rs.List() { 255 rule := rule.(map[string]interface{}) 256 uuids := rule["uuids"].(map[string]interface{}) 257 258 if rule["protocol"].(string) == "icmp" { 259 id, ok := uuids["icmp"] 260 if !ok { 261 continue 262 } 263 264 // Get the rule 265 r, ok := ruleMap[id.(string)] 266 if !ok { 267 delete(uuids, "icmp") 268 continue 269 } 270 271 // Delete the known rule so only unknown rules remain in the ruleMap 272 delete(ruleMap, id.(string)) 273 274 // Update the values 275 rule["action"] = strings.ToLower(r.Action) 276 rule["source_cidr"] = r.Cidrlist 277 rule["protocol"] = r.Protocol 278 rule["icmp_type"] = r.Icmptype 279 rule["icmp_code"] = r.Icmpcode 280 rule["traffic_type"] = strings.ToLower(r.Traffictype) 281 rules.Add(rule) 282 } 283 284 if rule["protocol"].(string) == "all" { 285 id, ok := uuids["all"] 286 if !ok { 287 continue 288 } 289 290 // Get the rule 291 r, ok := ruleMap[id.(string)] 292 if !ok { 293 delete(uuids, "all") 294 continue 295 } 296 297 // Delete the known rule so only unknown rules remain in the ruleMap 298 delete(ruleMap, id.(string)) 299 300 // Update the values 301 rule["action"] = strings.ToLower(r.Action) 302 rule["source_cidr"] = r.Cidrlist 303 rule["protocol"] = r.Protocol 304 rule["traffic_type"] = strings.ToLower(r.Traffictype) 305 rules.Add(rule) 306 } 307 308 // If protocol is tcp or udp, loop through all ports 309 if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" { 310 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 311 312 // Create an empty schema.Set to hold all ports 313 ports := &schema.Set{ 314 F: func(v interface{}) int { 315 return hashcode.String(v.(string)) 316 }, 317 } 318 319 // Loop through all ports and retrieve their info 320 for _, port := range ps.List() { 321 id, ok := uuids[port.(string)] 322 if !ok { 323 continue 324 } 325 326 // Get the rule 327 r, ok := ruleMap[id.(string)] 328 if !ok { 329 delete(uuids, port.(string)) 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["source_cidr"] = r.Cidrlist 339 rule["protocol"] = r.Protocol 340 rule["traffic_type"] = strings.ToLower(r.Traffictype) 341 ports.Add(port) 342 } 343 344 // If there is at least one port found, add this rule to the rules set 345 if ports.Len() > 0 { 346 rule["ports"] = ports 347 rules.Add(rule) 348 } 349 } 350 } 351 } 352 } 353 354 // If this is a managed firewall, add all unknown rules into a single dummy rule 355 managed := d.Get("managed").(bool) 356 if managed && len(ruleMap) > 0 { 357 // Add all UUIDs to a uuids map 358 uuids := make(map[string]interface{}, len(ruleMap)) 359 for uuid := range ruleMap { 360 uuids[uuid] = uuid 361 } 362 363 rule := map[string]interface{}{ 364 "source_cidr": "N/A", 365 "protocol": "N/A", 366 "uuids": uuids, 367 } 368 369 // Add the dummy rule to the rules set 370 rules.Add(rule) 371 } 372 373 if rules.Len() > 0 { 374 d.Set("rule", rules) 375 } else if !managed { 376 d.SetId("") 377 } 378 379 return nil 380 } 381 382 func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { 383 // Make sure all required parameters are there 384 if err := verifyNetworkACLParams(d); err != nil { 385 return err 386 } 387 388 // Check if the rule set as a whole has changed 389 if d.HasChange("rule") { 390 o, n := d.GetChange("rule") 391 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 392 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 393 394 // Now first loop through all the old rules and delete any obsolete ones 395 for _, rule := range ors.List() { 396 // Delete the rule as it no longer exists in the config 397 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 398 if err != nil { 399 return err 400 } 401 } 402 403 // Make sure we save the state of the currently configured rules 404 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 405 d.Set("rule", rules) 406 407 // Then loop through all the currently configured rules and create the new ones 408 for _, rule := range nrs.List() { 409 // When successfully deleted, re-create it again if it still exists 410 err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) 411 412 // We need to update this first to preserve the correct state 413 rules.Add(rule) 414 d.Set("rule", rules) 415 416 if err != nil { 417 return err 418 } 419 } 420 } 421 422 return resourceCloudStackNetworkACLRuleRead(d, meta) 423 } 424 425 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error { 426 // Delete all rules 427 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 428 for _, rule := range rs.List() { 429 // Delete a single rule 430 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 431 432 // We need to update this first to preserve the correct state 433 d.Set("rule", rs) 434 435 if err != nil { 436 return err 437 } 438 } 439 } 440 441 return nil 442 } 443 444 func resourceCloudStackNetworkACLRuleDeleteRule( 445 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 446 cs := meta.(*cloudstack.CloudStackClient) 447 uuids := rule["uuids"].(map[string]interface{}) 448 449 for k, id := range uuids { 450 // We don't care about the count here, so just continue 451 if k == "#" { 452 continue 453 } 454 455 // Create the parameter struct 456 p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string)) 457 458 // Delete the rule 459 if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil { 460 461 // This is a very poor way to be told the ID does no longer exist :( 462 if strings.Contains(err.Error(), fmt.Sprintf( 463 "Invalid parameter id value=%s due to incorrect long value format, "+ 464 "or entity does not exist", id.(string))) { 465 delete(uuids, k) 466 continue 467 } 468 469 return err 470 } 471 472 // Delete the UUID of this rule 473 delete(uuids, k) 474 } 475 476 // Update the UUIDs 477 rule["uuids"] = uuids 478 479 return nil 480 } 481 482 func resourceCloudStackNetworkACLRuleHash(v interface{}) int { 483 var buf bytes.Buffer 484 m := v.(map[string]interface{}) 485 486 // This is a little ugly, but it's needed because these arguments have 487 // a default value that needs to be part of the string to hash 488 var action, trafficType string 489 if a, ok := m["action"]; ok { 490 action = a.(string) 491 } else { 492 action = "allow" 493 } 494 if t, ok := m["traffic_type"]; ok { 495 trafficType = t.(string) 496 } else { 497 trafficType = "ingress" 498 } 499 500 buf.WriteString(fmt.Sprintf( 501 "%s-%s-%s-%s-", 502 action, 503 m["source_cidr"].(string), 504 m["protocol"].(string), 505 trafficType)) 506 507 if v, ok := m["icmp_type"]; ok { 508 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 509 } 510 511 if v, ok := m["icmp_code"]; ok { 512 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 513 } 514 515 // We need to make sure to sort the strings below so that we always 516 // generate the same hash code no matter what is in the set. 517 if v, ok := m["ports"]; ok { 518 vs := v.(*schema.Set).List() 519 s := make([]string, len(vs)) 520 521 for i, raw := range vs { 522 s[i] = raw.(string) 523 } 524 sort.Strings(s) 525 526 for _, v := range s { 527 buf.WriteString(fmt.Sprintf("%s-", v)) 528 } 529 } 530 531 return hashcode.String(buf.String()) 532 } 533 534 func verifyNetworkACLParams(d *schema.ResourceData) error { 535 managed := d.Get("managed").(bool) 536 _, rules := d.GetOk("rule") 537 538 if !rules && !managed { 539 return fmt.Errorf( 540 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 541 } 542 543 return nil 544 } 545 546 func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 547 action := rule["action"].(string) 548 if action != "allow" && action != "deny" { 549 return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") 550 } 551 552 protocol := rule["protocol"].(string) 553 switch protocol { 554 case "icmp": 555 if _, ok := rule["icmp_type"]; !ok { 556 return fmt.Errorf( 557 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 558 } 559 if _, ok := rule["icmp_code"]; !ok { 560 return fmt.Errorf( 561 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 562 } 563 case "all": 564 // No additional test are needed, so just leave this empty... 565 case "tcp", "udp": 566 if _, ok := rule["ports"]; !ok { 567 return fmt.Errorf( 568 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 569 } 570 default: 571 _, err := strconv.ParseInt(protocol, 0, 0) 572 if err != nil { 573 return fmt.Errorf( 574 "%s is not a valid protocol. Valid options are 'tcp', 'udp', "+ 575 "'icmp', 'all' or a valid protocol number", protocol) 576 } 577 } 578 579 traffic := rule["traffic_type"].(string) 580 if traffic != "ingress" && traffic != "egress" { 581 return fmt.Errorf( 582 "Parameter traffic_type only accepts 'ingress' or 'egress' as values") 583 } 584 585 return nil 586 } 587 588 func retryableACLCreationFunc( 589 cs *cloudstack.CloudStackClient, 590 p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) { 591 return func() (interface{}, error) { 592 r, err := cs.NetworkACL.CreateNetworkACL(p) 593 if err != nil { 594 return nil, err 595 } 596 return r, nil 597 } 598 }