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