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