github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.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 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 "source_cidr": &schema.Schema{ 42 Type: schema.TypeString, 43 Required: true, 44 }, 45 46 "protocol": &schema.Schema{ 47 Type: schema.TypeString, 48 Required: true, 49 }, 50 51 "icmp_type": &schema.Schema{ 52 Type: schema.TypeInt, 53 Optional: true, 54 Computed: true, 55 }, 56 57 "icmp_code": &schema.Schema{ 58 Type: schema.TypeInt, 59 Optional: true, 60 Computed: true, 61 }, 62 63 "ports": &schema.Schema{ 64 Type: schema.TypeSet, 65 Optional: true, 66 Elem: &schema.Schema{Type: schema.TypeString}, 67 Set: func(v interface{}) int { 68 return hashcode.String(v.(string)) 69 }, 70 }, 71 72 "uuids": &schema.Schema{ 73 Type: schema.TypeMap, 74 Computed: true, 75 }, 76 }, 77 }, 78 Set: resourceCloudStackEgressFirewallRuleHash, 79 }, 80 }, 81 } 82 } 83 84 func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error { 85 cs := meta.(*cloudstack.CloudStackClient) 86 87 // Make sure all required parameters are there 88 if err := verifyEgressFirewallParams(d); err != nil { 89 return err 90 } 91 92 // Retrieve the network UUID 93 networkid, e := retrieveUUID(cs, "network", d.Get("network").(string)) 94 if e != nil { 95 return e.Error() 96 } 97 98 // We need to set this upfront in order to be able to save a partial state 99 d.SetId(networkid) 100 101 // Create all rules that are configured 102 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 103 104 // Create an empty schema.Set to hold all rules 105 rules := &schema.Set{ 106 F: resourceCloudStackEgressFirewallRuleHash, 107 } 108 109 for _, rule := range rs.List() { 110 // Create a single rule 111 err := resourceCloudStackEgressFirewallCreateRule(d, meta, rule.(map[string]interface{})) 112 113 // We need to update this first to preserve the correct state 114 rules.Add(rule) 115 d.Set("rule", rules) 116 117 if err != nil { 118 return err 119 } 120 } 121 } 122 123 return resourceCloudStackEgressFirewallRead(d, meta) 124 } 125 126 func resourceCloudStackEgressFirewallCreateRule( 127 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 128 cs := meta.(*cloudstack.CloudStackClient) 129 uuids := rule["uuids"].(map[string]interface{}) 130 131 // Make sure all required rule parameters are there 132 if err := verifyEgressFirewallRuleParams(d, rule); err != nil { 133 return err 134 } 135 136 // Create a new parameter struct 137 p := cs.Firewall.NewCreateEgressFirewallRuleParams(d.Id(), rule["protocol"].(string)) 138 139 // Set the CIDR list 140 p.SetCidrlist([]string{rule["source_cidr"].(string)}) 141 142 // If the protocol is ICMP set the needed ICMP parameters 143 if rule["protocol"].(string) == "icmp" { 144 p.SetIcmptype(rule["icmp_type"].(int)) 145 p.SetIcmpcode(rule["icmp_code"].(int)) 146 147 r, err := cs.Firewall.CreateEgressFirewallRule(p) 148 if err != nil { 149 return err 150 } 151 uuids["icmp"] = r.Id 152 rule["uuids"] = uuids 153 } 154 155 // If protocol is not ICMP, loop through all ports 156 if rule["protocol"].(string) != "icmp" { 157 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 158 159 // Create an empty schema.Set to hold all processed ports 160 ports := &schema.Set{ 161 F: func(v interface{}) int { 162 return hashcode.String(v.(string)) 163 }, 164 } 165 166 for _, port := range ps.List() { 167 re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`) 168 m := re.FindStringSubmatch(port.(string)) 169 170 startPort, err := strconv.Atoi(m[1]) 171 if err != nil { 172 return err 173 } 174 175 endPort := startPort 176 if m[2] != "" { 177 endPort, err = strconv.Atoi(m[2]) 178 if err != nil { 179 return err 180 } 181 } 182 183 p.SetStartport(startPort) 184 p.SetEndport(endPort) 185 186 r, err := cs.Firewall.CreateEgressFirewallRule(p) 187 if err != nil { 188 return err 189 } 190 191 ports.Add(port) 192 rule["ports"] = ports 193 194 uuids[port.(string)] = r.Id 195 rule["uuids"] = uuids 196 } 197 } 198 } 199 200 return nil 201 } 202 203 func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error { 204 cs := meta.(*cloudstack.CloudStackClient) 205 206 // Create an empty schema.Set to hold all rules 207 rules := &schema.Set{ 208 F: resourceCloudStackEgressFirewallRuleHash, 209 } 210 211 // Read all rules that are configured 212 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 213 for _, rule := range rs.List() { 214 rule := rule.(map[string]interface{}) 215 uuids := rule["uuids"].(map[string]interface{}) 216 217 if rule["protocol"].(string) == "icmp" { 218 id, ok := uuids["icmp"] 219 if !ok { 220 continue 221 } 222 223 // Get the rule 224 r, count, err := cs.Firewall.GetEgressFirewallRuleByID(id.(string)) 225 // If the count == 0, there is no object found for this UUID 226 if err != nil { 227 if count == 0 { 228 delete(uuids, "icmp") 229 continue 230 } 231 232 return err 233 } 234 235 // Update the values 236 rule["source_cidr"] = r.Cidrlist 237 rule["protocol"] = r.Protocol 238 rule["icmp_type"] = r.Icmptype 239 rule["icmp_code"] = r.Icmpcode 240 rules.Add(rule) 241 } 242 243 // If protocol is not ICMP, loop through all ports 244 if rule["protocol"].(string) != "icmp" { 245 if ps := rule["ports"].(*schema.Set); ps.Len() > 0 { 246 247 // Create an empty schema.Set to hold all ports 248 ports := &schema.Set{ 249 F: func(v interface{}) int { 250 return hashcode.String(v.(string)) 251 }, 252 } 253 254 // Loop through all ports and retrieve their info 255 for _, port := range ps.List() { 256 id, ok := uuids[port.(string)] 257 if !ok { 258 continue 259 } 260 261 // Get the rule 262 r, count, err := cs.Firewall.GetEgressFirewallRuleByID(id.(string)) 263 if err != nil { 264 if count == 0 { 265 delete(uuids, port.(string)) 266 continue 267 } 268 269 return err 270 } 271 272 // Update the values 273 rule["source_cidr"] = r.Cidrlist 274 rule["protocol"] = r.Protocol 275 ports.Add(port) 276 } 277 278 // If there is at least one port found, add this rule to the rules set 279 if ports.Len() > 0 { 280 rule["ports"] = ports 281 rules.Add(rule) 282 } 283 } 284 } 285 } 286 } 287 288 // If this is a managed firewall, add all unknown rules into a single dummy rule 289 managed := d.Get("managed").(bool) 290 if managed { 291 // Get all the rules from the running environment 292 p := cs.Firewall.NewListEgressFirewallRulesParams() 293 p.SetNetworkid(d.Id()) 294 p.SetListall(true) 295 296 r, err := cs.Firewall.ListEgressFirewallRules(p) 297 if err != nil { 298 return err 299 } 300 301 // Add all UUIDs to the uuids map 302 uuids := make(map[string]interface{}, len(r.EgressFirewallRules)) 303 for _, r := range r.EgressFirewallRules { 304 uuids[r.Id] = r.Id 305 } 306 307 // Delete all expected UUIDs from the uuids map 308 for _, rule := range rules.List() { 309 rule := rule.(map[string]interface{}) 310 311 for _, id := range rule["uuids"].(map[string]interface{}) { 312 delete(uuids, id.(string)) 313 } 314 } 315 316 if len(uuids) > 0 { 317 // Make a dummy rule to hold all unknown UUIDs 318 rule := map[string]interface{}{ 319 "source_cidr": "N/A", 320 "protocol": "N/A", 321 "uuids": uuids, 322 } 323 324 // Add the dummy rule to the rules set 325 rules.Add(rule) 326 } 327 } 328 329 if rules.Len() > 0 { 330 d.Set("rule", rules) 331 } else if !managed { 332 d.SetId("") 333 } 334 335 return nil 336 } 337 338 func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error { 339 // Make sure all required parameters are there 340 if err := verifyEgressFirewallParams(d); err != nil { 341 return err 342 } 343 344 // Check if the rule set as a whole has changed 345 if d.HasChange("rule") { 346 o, n := d.GetChange("rule") 347 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 348 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 349 350 // Now first loop through all the old rules and delete any obsolete ones 351 for _, rule := range ors.List() { 352 // Delete the rule as it no longer exists in the config 353 err := resourceCloudStackEgressFirewallDeleteRule(d, meta, rule.(map[string]interface{})) 354 if err != nil { 355 return err 356 } 357 } 358 359 // Make sure we save the state of the currently configured rules 360 rules := o.(*schema.Set).Intersection(n.(*schema.Set)) 361 d.Set("rule", rules) 362 363 // Then loop through all the currently configured rules and create the new ones 364 for _, rule := range nrs.List() { 365 // When succesfully deleted, re-create it again if it still exists 366 err := resourceCloudStackEgressFirewallCreateRule( 367 d, meta, rule.(map[string]interface{})) 368 369 // We need to update this first to preserve the correct state 370 rules.Add(rule) 371 d.Set("rule", rules) 372 373 if err != nil { 374 return err 375 } 376 } 377 } 378 379 return resourceCloudStackEgressFirewallRead(d, meta) 380 } 381 382 func resourceCloudStackEgressFirewallDelete(d *schema.ResourceData, meta interface{}) error { 383 // Delete all rules 384 if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { 385 for _, rule := range rs.List() { 386 // Delete a single rule 387 err := resourceCloudStackEgressFirewallDeleteRule(d, meta, rule.(map[string]interface{})) 388 389 // We need to update this first to preserve the correct state 390 d.Set("rule", rs) 391 392 if err != nil { 393 return err 394 } 395 } 396 } 397 398 return nil 399 } 400 401 func resourceCloudStackEgressFirewallDeleteRule( 402 d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { 403 cs := meta.(*cloudstack.CloudStackClient) 404 uuids := rule["uuids"].(map[string]interface{}) 405 406 for k, id := range uuids { 407 // We don't care about the count here, so just continue 408 if k == "#" { 409 continue 410 } 411 412 // Create the parameter struct 413 p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string)) 414 415 // Delete the rule 416 if _, err := cs.Firewall.DeleteEgressFirewallRule(p); err != nil { 417 418 // This is a very poor way to be told the UUID does no longer exist :( 419 if strings.Contains(err.Error(), fmt.Sprintf( 420 "Invalid parameter id value=%s due to incorrect long value format, "+ 421 "or entity does not exist", id.(string))) { 422 delete(uuids, k) 423 continue 424 } 425 426 return err 427 } 428 429 // Delete the UUID of this rule 430 delete(uuids, k) 431 } 432 433 // Update the UUIDs 434 rule["uuids"] = uuids 435 436 return nil 437 } 438 439 func resourceCloudStackEgressFirewallRuleHash(v interface{}) int { 440 var buf bytes.Buffer 441 m := v.(map[string]interface{}) 442 buf.WriteString(fmt.Sprintf( 443 "%s-%s-", m["source_cidr"].(string), m["protocol"].(string))) 444 445 if v, ok := m["icmp_type"]; ok { 446 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 447 } 448 449 if v, ok := m["icmp_code"]; ok { 450 buf.WriteString(fmt.Sprintf("%d-", v.(int))) 451 } 452 453 // We need to make sure to sort the strings below so that we always 454 // generate the same hash code no matter what is in the set. 455 if v, ok := m["ports"]; ok { 456 vs := v.(*schema.Set).List() 457 s := make([]string, len(vs)) 458 459 for i, raw := range vs { 460 s[i] = raw.(string) 461 } 462 sort.Strings(s) 463 464 for _, v := range s { 465 buf.WriteString(fmt.Sprintf("%s-", v)) 466 } 467 } 468 469 return hashcode.String(buf.String()) 470 } 471 472 func verifyEgressFirewallParams(d *schema.ResourceData) error { 473 managed := d.Get("managed").(bool) 474 _, rules := d.GetOk("rule") 475 476 if !rules && !managed { 477 return fmt.Errorf( 478 "You must supply at least one 'rule' when not using the 'managed' firewall feature") 479 } 480 481 return nil 482 } 483 484 func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error { 485 protocol := rule["protocol"].(string) 486 if protocol != "tcp" && protocol != "udp" && protocol != "icmp" { 487 return fmt.Errorf( 488 "%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol) 489 } 490 491 if protocol == "icmp" { 492 if _, ok := rule["icmp_type"]; !ok { 493 return fmt.Errorf( 494 "Parameter icmp_type is a required parameter when using protocol 'icmp'") 495 } 496 if _, ok := rule["icmp_code"]; !ok { 497 return fmt.Errorf( 498 "Parameter icmp_code is a required parameter when using protocol 'icmp'") 499 } 500 } else { 501 if _, ok := rule["ports"]; !ok { 502 return fmt.Errorf( 503 "Parameter port is a required parameter when using protocol 'tcp' or 'udp'") 504 } 505 } 506 507 return nil 508 }