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