github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go (about) 1 package cloudstack 2 3 import ( 4 "fmt" 5 "regexp" 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 resourceCloudStackEgressFirewall() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceCloudStackEgressFirewallCreate, 19 Read: resourceCloudStackEgressFirewallRead, 20 Update: resourceCloudStackEgressFirewallUpdate, 21 Delete: resourceCloudStackEgressFirewallDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "network": &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 "cidr_list": &schema.Schema{ 42 Type: schema.TypeSet, 43 Optional: true, 44 Elem: &schema.Schema{Type: schema.TypeString}, 45 Set: schema.HashString, 46 }, 47 48 "source_cidr": &schema.Schema{ 49 Type: schema.TypeString, 50 Optional: true, 51 Deprecated: "Please use the `cidr_list` field instead", 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 "uuids": &schema.Schema{ 79 Type: schema.TypeMap, 80 Computed: true, 81 }, 82 }, 83 }, 84 }, 85 }, 86 } 87 } 88 89 func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error { 90 cs := meta.(*cloudstack.CloudStackClient) 91 92 // Make sure all required parameters are there 93 if err := verifyEgressFirewallParams(d); err != nil { 94 return err 95 } 96 97 // Retrieve the network ID 98 networkid, e := retrieveID(cs, "network", d.Get("network").(string)) 99 if e != nil { 100 return e.Error() 101 } 102 103 // We need to set this upfront in order to be able to save a partial state 104 d.SetId(networkid) 105 106 // Create all rules that are configured 107 if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 { 108 // Create an empty schema.Set to hold all rules 109 rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set) 110 111 err := createEgressFirewallRules(d, meta, rules, nrs) 112 113 // We need to update this first to preserve the correct state 114 d.Set("rule", rules) 115 116 if err != nil { 117 return err 118 } 119 } 120 121 return resourceCloudStackEgressFirewallRead(d, meta) 122 } 123 124 func createEgressFirewallRules( 125 d *schema.ResourceData, 126 meta interface{}, 127 rules *schema.Set, 128 nrs *schema.Set) error { 129 var errs *multierror.Error 130 131 var wg sync.WaitGroup 132 wg.Add(nrs.Len()) 133 134 sem := make(chan struct{}, 10) 135 for _, rule := range nrs.List() { 136 // Put in a tiny sleep here to avoid DoS'ing the API 137 time.Sleep(500 * time.Millisecond) 138 139 go func(rule map[string]interface{}) { 140 defer wg.Done() 141 sem <- struct{}{} 142 143 // Create a single rule 144 err := createEgressFirewallRule(d, meta, rule) 145 146 // If we have at least one UUID, we need to save the rule 147 if len(rule["uuids"].(map[string]interface{})) > 0 { 148 rules.Add(rule) 149 } 150 151 if err != nil { 152 errs = multierror.Append(errs, err) 153 } 154 155 <-sem 156 }(rule.(map[string]interface{})) 157 } 158 159 wg.Wait() 160 161 return errs.ErrorOrNil() 162 } 163 func createEgressFirewallRule( 164 d *schema.ResourceData, 165 meta interface{}, 166 rule map[string]interface{}) error { 167 cs := meta.(*cloudstack.CloudStackClient) 168 uuids := rule["uuids"].(map[string]interface{}) 169 170 // Make sure all required rule parameters are there 171 if err := verifyEgressFirewallRuleParams(d, rule); err != nil { 172 return err 173 } 174 175 // Create a new parameter struct 176 p := cs.Firewall.NewCreateEgressFirewallRuleParams(d.Id(), rule["protocol"].(string)) 177 178 // Set the CIDR list 179 p.SetCidrlist(retrieveCidrList(rule)) 180 181 // If the protocol is ICMP set the needed ICMP parameters 182 if rule["protocol"].(string) == "icmp" { 183 p.SetIcmptype(rule["icmp_type"].(int)) 184 p.SetIcmpcode(rule["icmp_code"].(int)) 185 186 r, err := cs.Firewall.CreateEgressFirewallRule(p) 187 if err != nil { 188 return err 189 } 190 uuids["icmp"] = r.Id 191 rule["uuids"] = uuids 192 } 193 194 // If protocol is not ICMP, loop through all ports 195 if rule["protocol"].(string) != "icmp" { 196 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 197 198 // Create an empty schema.Set to hold all processed ports 199 ports := &schema.Set{F: schema.HashString} 200 201 // Define a regexp for parsing the port 202 re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`) 203 204 for _, port := range ps.List() { 205 if _, ok := uuids[port.(string)]; ok { 206 ports.Add(port) 207 rule["ports"] = ports 208 continue 209 } 210 211 m := re.FindStringSubmatch(port.(string)) 212 213 startPort, err := strconv.Atoi(m[1]) 214 if err != nil { 215 return err 216 } 217 218 endPort := startPort 219 if m[2] != "" { 220 endPort, err = strconv.Atoi(m[2]) 221 if err != nil { 222 return err 223 } 224 } 225 226 p.SetStartport(startPort) 227 p.SetEndport(endPort) 228 229 r, err := cs.Firewall.CreateEgressFirewallRule(p) 230 if err != nil { 231 return err 232 } 233 234 ports.Add(port) 235 rule["ports"] = ports 236 237 uuids[port.(string)] = r.Id 238 rule["uuids"] = uuids 239 } 240 } 241 } 242 243 return nil 244 } 245 246 func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error { 247 cs := meta.(*cloudstack.CloudStackClient) 248 249 // Get all the rules from the running environment 250 p := cs.Firewall.NewListEgressFirewallRulesParams() 251 p.SetNetworkid(d.Id()) 252 p.SetListall(true) 253 254 l, err := cs.Firewall.ListEgressFirewallRules(p) 255 if err != nil { 256 return err 257 } 258 259 // Make a map of all the rules so we can easily find a rule 260 ruleMap := make(map[string]*cloudstack.EgressFirewallRule, l.Count) 261 for _, r := range l.EgressFirewallRules { 262 ruleMap[r.Id] = r 263 } 264 265 // Create an empty schema.Set to hold all rules 266 rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set) 267 268 // Read all rules that are configured 269 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 270 for _, rule := range rs.List() { 271 rule := rule.(map[string]interface{}) 272 uuids := rule["uuids"].(map[string]interface{}) 273 274 if rule["protocol"].(string) == "icmp" { 275 id, ok := uuids["icmp"] 276 if !ok { 277 continue 278 } 279 280 // Get the rule 281 r, ok := ruleMap[id.(string)] 282 if !ok { 283 delete(uuids, "icmp") 284 continue 285 } 286 287 // Delete the known rule so only unknown rules remain in the ruleMap 288 delete(ruleMap, id.(string)) 289 290 // Update the values 291 rule["protocol"] = r.Protocol 292 rule["icmp_type"] = r.Icmptype 293 rule["icmp_code"] = r.Icmpcode 294 setCidrList(rule, r.Cidrlist) 295 rules.Add(rule) 296 } 297 298 // If protocol is not ICMP, loop through all ports 299 if rule["protocol"].(string) != "icmp" { 300 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 301 302 // Create an empty schema.Set to hold all ports 303 ports := &schema.Set{F: schema.HashString} 304 305 // Loop through all ports and retrieve their info 306 for _, port := range ps.List() { 307 id, ok := uuids[port.(string)] 308 if !ok { 309 continue 310 } 311 312 // Get the rule 313 r, ok := ruleMap[id.(string)] 314 if !ok { 315 delete(uuids, port.(string)) 316 continue 317 } 318 319 // Delete the known rule so only unknown rules remain in the ruleMap 320 delete(ruleMap, id.(string)) 321 322 // Update the values 323 rule["protocol"] = r.Protocol 324 setCidrList(rule, r.Cidrlist) 325 ports.Add(port) 326 } 327 328 // If there is at least one port found, add this rule to the rules set 329 if ports.Len() > 0 { 330 rule["ports"] = ports 331 rules.Add(rule) 332 } 333 } 334 } 335 } 336 } 337 338 // If this is a managed firewall, add all unknown rules into a single dummy rule 339 managed := d.Get("managed").(bool) 340 if managed && len(ruleMap) > 0 { 341 for uuid := range ruleMap { 342 // We need to create and add a dummy value to a schema.Set as the 343 // cidr_list is a required field and thus needs a value 344 cidrs := &schema.Set{F: schema.HashString} 345 cidrs.Add(uuid) 346 347 // Make a dummy rule to hold the unknown UUID 348 rule := map[string]interface{}{ 349 "cidr_list": uuid, 350 "protocol": uuid, 351 "uuids": map[string]interface{}{uuid: uuid}, 352 } 353 354 // Add the dummy rule to the rules set 355 rules.Add(rule) 356 } 357 } 358 359 if rules.Len() > 0 { 360 d.Set("rule", rules) 361 } else if !managed { 362 d.SetId("") 363 } 364 365 return nil 366 } 367 368 func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error { 369 // Make sure all required parameters are there 370 if err := verifyEgressFirewallParams(d); err != nil { 371 return err 372 } 373 374 // Check if the rule set as a whole has changed 375 if d.HasChange("rule") { 376 o, n := d.GetChange("rule") 377 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 378 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 379 380 // We need to start with a rule set containing all the rules we 381 // already have and want to keep. Any rules that are not deleted 382 // correctly and any newly created rules, will be added to this 383 // set to make sure we end up in a consistent state 384 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 385 386 // First loop through all the old rules and delete them 387 if ors.Len() > 0 { 388 err := deleteEgressFirewallRules(d, meta, rules, ors) 389 390 // We need to update this first to preserve the correct state 391 d.Set("rule", rules) 392 393 if err != nil { 394 return err 395 } 396 } 397 398 // Then loop through all the new rules and create them 399 if nrs.Len() > 0 { 400 err := createEgressFirewallRules(d, meta, rules, nrs) 401 402 // We need to update this first to preserve the correct state 403 d.Set("rule", rules) 404 405 if err != nil { 406 return err 407 } 408 } 409 } 410 411 return resourceCloudStackEgressFirewallRead(d, meta) 412 } 413 414 func resourceCloudStackEgressFirewallDelete(d *schema.ResourceData, meta interface{}) error { 415 // Create an empty rule set to hold all rules that where 416 // not deleted correctly 417 rules := resourceCloudStackEgressFirewall().Schema["rule"].ZeroValue().(*schema.Set) 418 419 // Delete all rules 420 if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 { 421 err := deleteEgressFirewallRules(d, meta, rules, ors) 422 423 // We need to update this first to preserve the correct state 424 d.Set("rule", rules) 425 426 if err != nil { 427 return err 428 } 429 } 430 431 return nil 432 } 433 434 func deleteEgressFirewallRules( 435 d *schema.ResourceData, 436 meta interface{}, 437 rules *schema.Set, 438 ors *schema.Set) error { 439 var errs *multierror.Error 440 441 var wg sync.WaitGroup 442 wg.Add(ors.Len()) 443 444 sem := make(chan struct{}, 10) 445 for _, rule := range ors.List() { 446 // Put a sleep here to avoid DoS'ing the API 447 time.Sleep(500 * time.Millisecond) 448 449 go func(rule map[string]interface{}) { 450 defer wg.Done() 451 sem <- struct{}{} 452 453 // Delete a single rule 454 err := deleteEgressFirewallRule(d, meta, rule) 455 456 // If we have at least one UUID, we need to save the rule 457 if len(rule["uuids"].(map[string]interface{})) > 0 { 458 rules.Add(rule) 459 } 460 461 if err != nil { 462 errs = multierror.Append(errs, err) 463 } 464 465 <-sem 466 }(rule.(map[string]interface{})) 467 } 468 469 wg.Wait() 470 471 return errs.ErrorOrNil() 472 } 473 474 func deleteEgressFirewallRule( 475 d *schema.ResourceData, 476 meta interface{}, 477 rule map[string]interface{}) error { 478 cs := meta.(*cloudstack.CloudStackClient) 479 uuids := rule["uuids"].(map[string]interface{}) 480 481 for k, id := range uuids { 482 // We don't care about the count here, so just continue 483 if k == "#" { 484 continue 485 } 486 487 // Create the parameter struct 488 p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string)) 489 490 // Delete the rule 491 if _, err := cs.Firewall.DeleteEgressFirewallRule(p); err != nil { 492 493 // This is a very poor way to be told the ID does no longer exist :( 494 if strings.Contains(err.Error(), fmt.Sprintf( 495 "Invalid parameter id value=%s due to incorrect long value format, "+ 496 "or entity does not exist", id.(string))) { 497 delete(uuids, k) 498 continue 499 } 500 501 return err 502 } 503 504 // Delete the UUID of this rule 505 delete(uuids, k) 506 rule["uuids"] = uuids 507 } 508 509 return nil 510 } 511 512 func verifyEgressFirewallParams(d *schema.ResourceData) error { 513 managed := d.Get("managed").(bool) 514 _, rules := d.GetOk("rule") 515 516 if !rules && !managed { 517 return fmt.Errorf( 518 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 519 } 520 521 return nil 522 } 523 524 func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 525 cidrList := rule["cidr_list"].(*schema.Set) 526 sourceCidr := rule["source_cidr"].(string) 527 if cidrList.Len() == 0 && sourceCidr == "" { 528 return fmt.Errorf( 529 "Parameter cidr_list is a required parameter") 530 } 531 if cidrList.Len() > 0 && sourceCidr != "" { 532 return fmt.Errorf( 533 "Parameter source_cidr is deprecated and cannot be used together with cidr_list") 534 } 535 536 protocol := rule["protocol"].(string) 537 if protocol != "tcp" && protocol != "udp" && protocol != "icmp" { 538 return fmt.Errorf( 539 "%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol) 540 } 541 542 if protocol == "icmp" { 543 if _, ok := rule["icmp_type"]; !ok { 544 return fmt.Errorf( 545 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 546 } 547 if _, ok := rule["icmp_code"]; !ok { 548 return fmt.Errorf( 549 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 550 } 551 } else { 552 if _, ok := rule["ports"]; !ok { 553 return fmt.Errorf( 554 "Parameter port is a required parameter when using protocol 'tcp' or 'udp'") 555 } 556 } 557 558 return nil 559 }