github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go (about) 1 package openstack 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "strings" 8 "time" 9 10 "github.com/hashicorp/terraform/helper/hashcode" 11 "github.com/hashicorp/terraform/helper/resource" 12 "github.com/hashicorp/terraform/helper/schema" 13 "github.com/rackspace/gophercloud" 14 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" 15 ) 16 17 func resourceComputeSecGroupV2() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceComputeSecGroupV2Create, 20 Read: resourceComputeSecGroupV2Read, 21 Update: resourceComputeSecGroupV2Update, 22 Delete: resourceComputeSecGroupV2Delete, 23 24 Schema: map[string]*schema.Schema{ 25 "region": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), 30 }, 31 "name": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 ForceNew: false, 35 }, 36 "description": &schema.Schema{ 37 Type: schema.TypeString, 38 Required: true, 39 ForceNew: false, 40 }, 41 "rule": &schema.Schema{ 42 Type: schema.TypeSet, 43 Optional: true, 44 Computed: true, 45 Elem: &schema.Resource{ 46 Schema: map[string]*schema.Schema{ 47 "id": &schema.Schema{ 48 Type: schema.TypeString, 49 Computed: true, 50 }, 51 "from_port": &schema.Schema{ 52 Type: schema.TypeInt, 53 Required: true, 54 ForceNew: false, 55 }, 56 "to_port": &schema.Schema{ 57 Type: schema.TypeInt, 58 Required: true, 59 ForceNew: false, 60 }, 61 "ip_protocol": &schema.Schema{ 62 Type: schema.TypeString, 63 Required: true, 64 ForceNew: false, 65 }, 66 "cidr": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: false, 70 StateFunc: func(v interface{}) string { 71 return strings.ToLower(v.(string)) 72 }, 73 }, 74 "from_group_id": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 ForceNew: false, 78 }, 79 "self": &schema.Schema{ 80 Type: schema.TypeBool, 81 Optional: true, 82 Default: false, 83 ForceNew: false, 84 }, 85 }, 86 }, 87 Set: secgroupRuleV2Hash, 88 }, 89 }, 90 } 91 } 92 93 func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) error { 94 config := meta.(*Config) 95 computeClient, err := config.computeV2Client(d.Get("region").(string)) 96 if err != nil { 97 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 98 } 99 100 // Before creating the security group, make sure all rules are valid. 101 if err := checkSecGroupV2RulesForErrors(d); err != nil { 102 return err 103 } 104 105 // If all rules are valid, proceed with creating the security gruop. 106 createOpts := secgroups.CreateOpts{ 107 Name: d.Get("name").(string), 108 Description: d.Get("description").(string), 109 } 110 111 log.Printf("[DEBUG] Create Options: %#v", createOpts) 112 sg, err := secgroups.Create(computeClient, createOpts).Extract() 113 if err != nil { 114 return fmt.Errorf("Error creating OpenStack security group: %s", err) 115 } 116 117 d.SetId(sg.ID) 118 119 // Now that the security group has been created, iterate through each rule and create it 120 createRuleOptsList := resourceSecGroupRulesV2(d) 121 for _, createRuleOpts := range createRuleOptsList { 122 _, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() 123 if err != nil { 124 return fmt.Errorf("Error creating OpenStack security group rule: %s", err) 125 } 126 } 127 128 return resourceComputeSecGroupV2Read(d, meta) 129 } 130 131 func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) error { 132 config := meta.(*Config) 133 computeClient, err := config.computeV2Client(d.Get("region").(string)) 134 if err != nil { 135 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 136 } 137 138 sg, err := secgroups.Get(computeClient, d.Id()).Extract() 139 if err != nil { 140 return CheckDeleted(d, err, "security group") 141 } 142 143 d.Set("name", sg.Name) 144 d.Set("description", sg.Description) 145 146 rtm, err := rulesToMap(computeClient, d, sg.Rules) 147 if err != nil { 148 return err 149 } 150 log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm) 151 d.Set("rule", rtm) 152 153 return nil 154 } 155 156 func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) error { 157 config := meta.(*Config) 158 computeClient, err := config.computeV2Client(d.Get("region").(string)) 159 if err != nil { 160 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 161 } 162 163 updateOpts := secgroups.UpdateOpts{ 164 Name: d.Get("name").(string), 165 Description: d.Get("description").(string), 166 } 167 168 log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) 169 170 _, err = secgroups.Update(computeClient, d.Id(), updateOpts).Extract() 171 if err != nil { 172 return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) 173 } 174 175 if d.HasChange("rule") { 176 oldSGRaw, newSGRaw := d.GetChange("rule") 177 oldSGRSet, newSGRSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) 178 secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet) 179 secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet) 180 181 log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd) 182 log.Printf("[DEBUG] Security groups rules to remove: %v", secgrouprulesToRemove) 183 184 for _, rawRule := range secgrouprulesToAdd.List() { 185 createRuleOpts := resourceSecGroupRuleCreateOptsV2(d, rawRule) 186 rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() 187 if err != nil { 188 return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err) 189 } 190 log.Printf("[DEBUG] Added rule (%s) to OpenStack security group (%s) ", rule.ID, d.Id()) 191 } 192 193 for _, r := range secgrouprulesToRemove.List() { 194 rule := resourceSecGroupRuleV2(d, r) 195 err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr() 196 if err != nil { 197 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 198 if !ok { 199 return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) 200 } 201 if errCode.Actual == 404 { 202 continue 203 } else { 204 return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id()) 205 } 206 } else { 207 log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) 208 } 209 } 210 } 211 212 return resourceComputeSecGroupV2Read(d, meta) 213 } 214 215 func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error { 216 config := meta.(*Config) 217 computeClient, err := config.computeV2Client(d.Get("region").(string)) 218 if err != nil { 219 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 220 } 221 222 stateConf := &resource.StateChangeConf{ 223 Pending: []string{"ACTIVE"}, 224 Target: []string{"DELETED"}, 225 Refresh: SecGroupV2StateRefreshFunc(computeClient, d), 226 Timeout: 10 * time.Minute, 227 Delay: 10 * time.Second, 228 MinTimeout: 3 * time.Second, 229 } 230 231 _, err = stateConf.WaitForState() 232 if err != nil { 233 return fmt.Errorf("Error deleting OpenStack security group: %s", err) 234 } 235 236 d.SetId("") 237 return nil 238 } 239 240 func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts { 241 rawRules := d.Get("rule").(*schema.Set).List() 242 createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules)) 243 for i, rawRule := range rawRules { 244 createRuleOptsList[i] = resourceSecGroupRuleCreateOptsV2(d, rawRule) 245 } 246 return createRuleOptsList 247 } 248 249 func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, rawRule interface{}) secgroups.CreateRuleOpts { 250 rawRuleMap := rawRule.(map[string]interface{}) 251 groupId := rawRuleMap["from_group_id"].(string) 252 if rawRuleMap["self"].(bool) { 253 groupId = d.Id() 254 } 255 return secgroups.CreateRuleOpts{ 256 ParentGroupID: d.Id(), 257 FromPort: rawRuleMap["from_port"].(int), 258 ToPort: rawRuleMap["to_port"].(int), 259 IPProtocol: rawRuleMap["ip_protocol"].(string), 260 CIDR: rawRuleMap["cidr"].(string), 261 FromGroupID: groupId, 262 } 263 } 264 265 func checkSecGroupV2RulesForErrors(d *schema.ResourceData) error { 266 rawRules := d.Get("rule").(*schema.Set).List() 267 for _, rawRule := range rawRules { 268 rawRuleMap := rawRule.(map[string]interface{}) 269 270 // only one of cidr, from_group_id, or self can be set 271 cidr := rawRuleMap["cidr"].(string) 272 groupId := rawRuleMap["from_group_id"].(string) 273 self := rawRuleMap["self"].(bool) 274 errorMessage := fmt.Errorf("Only one of cidr, from_group_id, or self can be set.") 275 276 // if cidr is set, from_group_id and self cannot be set 277 if cidr != "" { 278 if groupId != "" || self { 279 return errorMessage 280 } 281 } 282 283 // if from_group_id is set, cidr and self cannot be set 284 if groupId != "" { 285 if cidr != "" || self { 286 return errorMessage 287 } 288 } 289 290 // if self is set, cidr and from_group_id cannot be set 291 if self { 292 if cidr != "" || groupId != "" { 293 return errorMessage 294 } 295 } 296 } 297 298 return nil 299 } 300 301 func resourceSecGroupRuleV2(d *schema.ResourceData, rawRule interface{}) secgroups.Rule { 302 rawRuleMap := rawRule.(map[string]interface{}) 303 return secgroups.Rule{ 304 ID: rawRuleMap["id"].(string), 305 ParentGroupID: d.Id(), 306 FromPort: rawRuleMap["from_port"].(int), 307 ToPort: rawRuleMap["to_port"].(int), 308 IPProtocol: rawRuleMap["ip_protocol"].(string), 309 IPRange: secgroups.IPRange{CIDR: rawRuleMap["cidr"].(string)}, 310 } 311 } 312 313 func rulesToMap(computeClient *gophercloud.ServiceClient, d *schema.ResourceData, sgrs []secgroups.Rule) ([]map[string]interface{}, error) { 314 sgrMap := make([]map[string]interface{}, len(sgrs)) 315 for i, sgr := range sgrs { 316 groupId := "" 317 self := false 318 if sgr.Group.Name != "" { 319 if sgr.Group.Name == d.Get("name").(string) { 320 self = true 321 } else { 322 // Since Nova only returns the secgroup Name (and not the ID) for the group attribute, 323 // we need to look up all security groups and match the name. 324 // Nevermind that Nova wants the ID when setting the Group *and* that multiple groups 325 // with the same name can exist... 326 allPages, err := secgroups.List(computeClient).AllPages() 327 if err != nil { 328 return nil, err 329 } 330 securityGroups, err := secgroups.ExtractSecurityGroups(allPages) 331 if err != nil { 332 return nil, err 333 } 334 335 for _, sg := range securityGroups { 336 if sg.Name == sgr.Group.Name { 337 groupId = sg.ID 338 } 339 } 340 } 341 } 342 343 sgrMap[i] = map[string]interface{}{ 344 "id": sgr.ID, 345 "from_port": sgr.FromPort, 346 "to_port": sgr.ToPort, 347 "ip_protocol": sgr.IPProtocol, 348 "cidr": sgr.IPRange.CIDR, 349 "self": self, 350 "from_group_id": groupId, 351 } 352 } 353 return sgrMap, nil 354 } 355 356 func secgroupRuleV2Hash(v interface{}) int { 357 var buf bytes.Buffer 358 m := v.(map[string]interface{}) 359 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 360 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 361 buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) 362 buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["cidr"].(string)))) 363 buf.WriteString(fmt.Sprintf("%s-", m["from_group_id"].(string))) 364 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 365 366 return hashcode.String(buf.String()) 367 } 368 369 func SecGroupV2StateRefreshFunc(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { 370 return func() (interface{}, string, error) { 371 log.Printf("[DEBUG] Attempting to delete Security Group %s.\n", d.Id()) 372 373 err := secgroups.Delete(computeClient, d.Id()).ExtractErr() 374 if err != nil { 375 return nil, "", err 376 } 377 378 s, err := secgroups.Get(computeClient, d.Id()).Extract() 379 if err != nil { 380 err = CheckDeleted(d, err, "Security Group") 381 if err != nil { 382 return s, "", err 383 } else { 384 log.Printf("[DEBUG] Successfully deleted Security Group %s", d.Id()) 385 return s, "DELETED", nil 386 } 387 } 388 389 log.Printf("[DEBUG] Security Group %s still active.\n", d.Id()) 390 return s, "ACTIVE", nil 391 } 392 }