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