github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/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 := cs.NetworkACL.CreateNetworkACL(p) 161 if err != nil { 162 return err 163 } 164 uuids["icmp"] = r.Id 165 rule["uuids"] = uuids 166 } 167 168 // If protocol is not ICMP, loop through all ports 169 if rule["protocol"].(string) != "icmp" { 170 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 171 172 // Create an empty schema.Set to hold all processed ports 173 ports := &schema.Set{ 174 F: func(v interface{}) int { 175 return hashcode.String(v.(string)) 176 }, 177 } 178 179 for _, port := range ps.List() { 180 re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`) 181 m := re.FindStringSubmatch(port.(string)) 182 183 startPort, err := strconv.Atoi(m[1]) 184 if err != nil { 185 return err 186 } 187 188 endPort := startPort 189 if m[2] != "" { 190 endPort, err = strconv.Atoi(m[2]) 191 if err != nil { 192 return err 193 } 194 } 195 196 p.SetStartport(startPort) 197 p.SetEndport(endPort) 198 199 r, err := cs.NetworkACL.CreateNetworkACL(p) 200 if err != nil { 201 return err 202 } 203 204 ports.Add(port) 205 rule["ports"] = ports 206 207 uuids[port.(string)] = r.Id 208 rule["uuids"] = uuids 209 } 210 } 211 } 212 213 return nil 214 } 215 216 func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error { 217 cs := meta.(*cloudstack.CloudStackClient) 218 219 // Create an empty schema.Set to hold all rules 220 rules := &schema.Set{ 221 F: resourceCloudStackNetworkACLRuleHash, 222 } 223 224 // Read all rules that are configured 225 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 226 for _, rule := range rs.List() { 227 rule := rule.(map[string]interface{}) 228 uuids := rule["uuids"].(map[string]interface{}) 229 230 if rule["protocol"].(string) == "icmp" { 231 id, ok := uuids["icmp"] 232 if !ok { 233 continue 234 } 235 236 // Get the rule 237 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 238 // If the count == 0, there is no object found for this UUID 239 if err != nil { 240 if count == 0 { 241 delete(uuids, "icmp") 242 continue 243 } 244 245 return err 246 } 247 248 // Update the values 249 rule["action"] = r.Action 250 rule["source_cidr"] = r.Cidrlist 251 rule["protocol"] = r.Protocol 252 rule["icmp_type"] = r.Icmptype 253 rule["icmp_code"] = r.Icmpcode 254 rule["traffic_type"] = r.Traffictype 255 rules.Add(rule) 256 } 257 258 // If protocol is not ICMP, loop through all ports 259 if rule["protocol"].(string) != "icmp" { 260 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 261 262 // Create an empty schema.Set to hold all ports 263 ports := &schema.Set{ 264 F: func(v interface{}) int { 265 return hashcode.String(v.(string)) 266 }, 267 } 268 269 // Loop through all ports and retrieve their info 270 for _, port := range ps.List() { 271 id, ok := uuids[port.(string)] 272 if !ok { 273 continue 274 } 275 276 // Get the rule 277 r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string)) 278 if err != nil { 279 if count == 0 { 280 delete(uuids, port.(string)) 281 continue 282 } 283 284 return err 285 } 286 287 // Update the values 288 rule["action"] = strings.ToLower(r.Action) 289 rule["source_cidr"] = r.Cidrlist 290 rule["protocol"] = r.Protocol 291 rule["traffic_type"] = strings.ToLower(r.Traffictype) 292 ports.Add(port) 293 } 294 295 // If there is at least one port found, add this rule to the rules set 296 if ports.Len() > 0 { 297 rule["ports"] = ports 298 rules.Add(rule) 299 } 300 } 301 } 302 } 303 } 304 305 // If this is a managed firewall, add all unknown rules into a single dummy rule 306 managed := d.Get("managed").(bool) 307 if managed { 308 // Get all the rules from the running environment 309 p := cs.NetworkACL.NewListNetworkACLsParams() 310 p.SetAclid(d.Id()) 311 p.SetListall(true) 312 313 r, err := cs.NetworkACL.ListNetworkACLs(p) 314 if err != nil { 315 return err 316 } 317 318 // Add all UUIDs to the uuids map 319 uuids := make(map[string]interface{}, len(r.NetworkACLs)) 320 for _, r := range r.NetworkACLs { 321 uuids[r.Id] = r.Id 322 } 323 324 // Delete all expected UUIDs from the uuids map 325 for _, rule := range rules.List() { 326 rule := rule.(map[string]interface{}) 327 328 for _, id := range rule["uuids"].(map[string]interface{}) { 329 delete(uuids, id.(string)) 330 } 331 } 332 333 if len(uuids) > 0 { 334 // Make a dummy rule to hold all unknown UUIDs 335 rule := map[string]interface{}{ 336 "source_cidr": "N/A", 337 "protocol": "N/A", 338 "uuids": uuids, 339 } 340 341 // Add the dummy rule to the rules set 342 rules.Add(rule) 343 } 344 } 345 346 if rules.Len() > 0 { 347 d.Set("rule", rules) 348 } else if !managed { 349 d.SetId("") 350 } 351 352 return nil 353 } 354 355 func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error { 356 // Make sure all required parameters are there 357 if err := verifyNetworkACLParams(d); err != nil { 358 return err 359 } 360 361 // Check if the rule set as a whole has changed 362 if d.HasChange("rule") { 363 o, n := d.GetChange("rule") 364 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 365 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 366 367 // Now first loop through all the old rules and delete any obsolete ones 368 for _, rule := range ors.List() { 369 // Delete the rule as it no longer exists in the config 370 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 371 if err != nil { 372 return err 373 } 374 } 375 376 // Make sure we save the state of the currently configured rules 377 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 378 d.Set("rule", rules) 379 380 // Then loop through al the currently configured rules and create the new ones 381 for _, rule := range nrs.List() { 382 // When succesfully deleted, re-create it again if it still exists 383 err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{})) 384 385 // We need to update this first to preserve the correct state 386 rules.Add(rule) 387 d.Set("rule", rules) 388 389 if err != nil { 390 return err 391 } 392 } 393 } 394 395 return resourceCloudStackNetworkACLRuleRead(d, meta) 396 } 397 398 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error { 399 // Delete all rules 400 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 401 for _, rule := range rs.List() { 402 // Delete a single rule 403 err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{})) 404 405 // We need to update this first to preserve the correct state 406 d.Set("rule", rs) 407 408 if err != nil { 409 return err 410 } 411 } 412 } 413 414 return nil 415 } 416 417 func resourceCloudStackNetworkACLRuleDeleteRule( 418 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 419 cs := meta.(*cloudstack.CloudStackClient) 420 uuids := rule["uuids"].(map[string]interface{}) 421 422 for k, id := range uuids { 423 // We don't care about the count here, so just continue 424 if k == "#" { 425 continue 426 } 427 428 // Create the parameter struct 429 p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string)) 430 431 // Delete the rule 432 if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil { 433 434 // This is a very poor way to be told the UUID does no longer exist :( 435 if strings.Contains(err.Error(), fmt.Sprintf( 436 "Invalid parameter id value=%s due to incorrect long value format, "+ 437 "or entity does not exist", id.(string))) { 438 delete(uuids, k) 439 continue 440 } 441 442 return err 443 } 444 445 // Delete the UUID of this rule 446 delete(uuids, k) 447 } 448 449 // Update the UUIDs 450 rule["uuids"] = uuids 451 452 return nil 453 } 454 455 func resourceCloudStackNetworkACLRuleHash(v interface{}) int { 456 var buf bytes.Buffer 457 m := v.(map[string]interface{}) 458 459 // This is a little ugly, but it's needed because these arguments have 460 // a default value that needs to be part of the string to hash 461 var action, trafficType string 462 if a, ok := m["action"]; ok { 463 action = a.(string) 464 } else { 465 action = "allow" 466 } 467 if t, ok := m["traffic_type"]; ok { 468 trafficType = t.(string) 469 } else { 470 trafficType = "ingress" 471 } 472 473 buf.WriteString(fmt.Sprintf( 474 "%s-%s-%s-%s-", 475 action, 476 m["source_cidr"].(string), 477 m["protocol"].(string), 478 trafficType)) 479 480 if v, ok := m["icmp_type"]; ok { 481 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 482 } 483 484 if v, ok := m["icmp_code"]; ok { 485 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 486 } 487 488 // We need to make sure to sort the strings below so that we always 489 // generate the same hash code no matter what is in the set. 490 if v, ok := m["ports"]; ok { 491 vs := v.(*schema.Set).List() 492 s := make([]string, len(vs)) 493 494 for i, raw := range vs { 495 s[i] = raw.(string) 496 } 497 sort.Strings(s) 498 499 for _, v := range s { 500 buf.WriteString(fmt.Sprintf("%s-", v)) 501 } 502 } 503 504 return hashcode.String(buf.String()) 505 } 506 507 func verifyNetworkACLParams(d *schema.ResourceData) error { 508 managed := d.Get("managed").(bool) 509 _, rules := d.GetOk("rule") 510 511 if !rules && !managed { 512 return fmt.Errorf( 513 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 514 } 515 516 return nil 517 } 518 519 func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 520 action := rule["action"].(string) 521 if action != "allow" && action != "deny" { 522 return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") 523 } 524 525 protocol := rule["protocol"].(string) 526 if protocol == "icmp" { 527 if _, ok := rule["icmp_type"]; !ok { 528 return fmt.Errorf( 529 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 530 } 531 if _, ok := rule["icmp_code"]; !ok { 532 return fmt.Errorf( 533 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 534 } 535 } else { 536 if protocol != "tcp" && protocol != "udp" && protocol != "all" { 537 _, err := strconv.ParseInt(protocol, 0, 0) 538 if err != nil { 539 return fmt.Errorf( 540 "%s is not a valid protocol. Valid options are 'tcp', 'udp', "+ 541 "'icmp', 'all' or a valid protocol number", protocol) 542 } 543 } 544 if _, ok := rule["ports"]; !ok { 545 return fmt.Errorf( 546 "Parameter ports is a required parameter when *not* using protocol 'icmp'") 547 } 548 } 549 550 traffic := rule["traffic_type"].(string) 551 if traffic != "ingress" && traffic != "egress" { 552 return fmt.Errorf( 553 "Parameter traffic_type only accepts 'ingress' or 'egress' as values") 554 } 555 556 return nil 557 }