github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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/gophercloud/gophercloud" 11 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 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(GetRegion(d)) 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(GetRegion(d)) 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 d.Set("region", GetRegion(d)) 157 158 return nil 159 } 160 161 func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) error { 162 config := meta.(*Config) 163 computeClient, err := config.computeV2Client(GetRegion(d)) 164 if err != nil { 165 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 166 } 167 168 updateOpts := secgroups.UpdateOpts{ 169 Name: d.Get("name").(string), 170 Description: d.Get("description").(string), 171 } 172 173 log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) 174 175 _, err = secgroups.Update(computeClient, d.Id(), updateOpts).Extract() 176 if err != nil { 177 return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) 178 } 179 180 if d.HasChange("rule") { 181 oldSGRaw, newSGRaw := d.GetChange("rule") 182 oldSGRSet, newSGRSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) 183 secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet) 184 secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet) 185 186 log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd) 187 log.Printf("[DEBUG] Security groups rules to remove: %v", secgrouprulesToRemove) 188 189 for _, rawRule := range secgrouprulesToAdd.List() { 190 createRuleOpts := resourceSecGroupRuleCreateOptsV2(d, rawRule) 191 rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() 192 if err != nil { 193 return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err) 194 } 195 log.Printf("[DEBUG] Added rule (%s) to OpenStack security group (%s) ", rule.ID, d.Id()) 196 } 197 198 for _, r := range secgrouprulesToRemove.List() { 199 rule := resourceSecGroupRuleV2(d, r) 200 err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr() 201 if err != nil { 202 if _, ok := err.(gophercloud.ErrDefault404); ok { 203 continue 204 } 205 206 return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id()) 207 } else { 208 log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) 209 } 210 } 211 } 212 213 return resourceComputeSecGroupV2Read(d, meta) 214 } 215 216 func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error { 217 config := meta.(*Config) 218 computeClient, err := config.computeV2Client(GetRegion(d)) 219 if err != nil { 220 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 221 } 222 223 stateConf := &resource.StateChangeConf{ 224 Pending: []string{"ACTIVE"}, 225 Target: []string{"DELETED"}, 226 Refresh: SecGroupV2StateRefreshFunc(computeClient, d), 227 Timeout: 10 * time.Minute, 228 Delay: 10 * time.Second, 229 MinTimeout: 3 * time.Second, 230 } 231 232 _, err = stateConf.WaitForState() 233 if err != nil { 234 return fmt.Errorf("Error deleting OpenStack security group: %s", err) 235 } 236 237 d.SetId("") 238 return nil 239 } 240 241 func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts { 242 rawRules := d.Get("rule").(*schema.Set).List() 243 createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules)) 244 for i, rawRule := range rawRules { 245 createRuleOptsList[i] = resourceSecGroupRuleCreateOptsV2(d, rawRule) 246 } 247 return createRuleOptsList 248 } 249 250 func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, rawRule interface{}) secgroups.CreateRuleOpts { 251 rawRuleMap := rawRule.(map[string]interface{}) 252 groupId := rawRuleMap["from_group_id"].(string) 253 if rawRuleMap["self"].(bool) { 254 groupId = d.Id() 255 } 256 return secgroups.CreateRuleOpts{ 257 ParentGroupID: d.Id(), 258 FromPort: rawRuleMap["from_port"].(int), 259 ToPort: rawRuleMap["to_port"].(int), 260 IPProtocol: rawRuleMap["ip_protocol"].(string), 261 CIDR: rawRuleMap["cidr"].(string), 262 FromGroupID: groupId, 263 } 264 } 265 266 func checkSecGroupV2RulesForErrors(d *schema.ResourceData) error { 267 rawRules := d.Get("rule").(*schema.Set).List() 268 for _, rawRule := range rawRules { 269 rawRuleMap := rawRule.(map[string]interface{}) 270 271 // only one of cidr, from_group_id, or self can be set 272 cidr := rawRuleMap["cidr"].(string) 273 groupId := rawRuleMap["from_group_id"].(string) 274 self := rawRuleMap["self"].(bool) 275 errorMessage := fmt.Errorf("Only one of cidr, from_group_id, or self can be set.") 276 277 // if cidr is set, from_group_id and self cannot be set 278 if cidr != "" { 279 if groupId != "" || self { 280 return errorMessage 281 } 282 } 283 284 // if from_group_id is set, cidr and self cannot be set 285 if groupId != "" { 286 if cidr != "" || self { 287 return errorMessage 288 } 289 } 290 291 // if self is set, cidr and from_group_id cannot be set 292 if self { 293 if cidr != "" || groupId != "" { 294 return errorMessage 295 } 296 } 297 } 298 299 return nil 300 } 301 302 func resourceSecGroupRuleV2(d *schema.ResourceData, rawRule interface{}) secgroups.Rule { 303 rawRuleMap := rawRule.(map[string]interface{}) 304 return secgroups.Rule{ 305 ID: rawRuleMap["id"].(string), 306 ParentGroupID: d.Id(), 307 FromPort: rawRuleMap["from_port"].(int), 308 ToPort: rawRuleMap["to_port"].(int), 309 IPProtocol: rawRuleMap["ip_protocol"].(string), 310 IPRange: secgroups.IPRange{CIDR: rawRuleMap["cidr"].(string)}, 311 } 312 } 313 314 func rulesToMap(computeClient *gophercloud.ServiceClient, d *schema.ResourceData, sgrs []secgroups.Rule) ([]map[string]interface{}, error) { 315 sgrMap := make([]map[string]interface{}, len(sgrs)) 316 for i, sgr := range sgrs { 317 groupId := "" 318 self := false 319 if sgr.Group.Name != "" { 320 if sgr.Group.Name == d.Get("name").(string) { 321 self = true 322 } else { 323 // Since Nova only returns the secgroup Name (and not the ID) for the group attribute, 324 // we need to look up all security groups and match the name. 325 // Nevermind that Nova wants the ID when setting the Group *and* that multiple groups 326 // with the same name can exist... 327 allPages, err := secgroups.List(computeClient).AllPages() 328 if err != nil { 329 return nil, err 330 } 331 securityGroups, err := secgroups.ExtractSecurityGroups(allPages) 332 if err != nil { 333 return nil, err 334 } 335 336 for _, sg := range securityGroups { 337 if sg.Name == sgr.Group.Name { 338 groupId = sg.ID 339 } 340 } 341 } 342 } 343 344 sgrMap[i] = map[string]interface{}{ 345 "id": sgr.ID, 346 "from_port": sgr.FromPort, 347 "to_port": sgr.ToPort, 348 "ip_protocol": sgr.IPProtocol, 349 "cidr": sgr.IPRange.CIDR, 350 "self": self, 351 "from_group_id": groupId, 352 } 353 } 354 return sgrMap, nil 355 } 356 357 func secgroupRuleV2Hash(v interface{}) int { 358 var buf bytes.Buffer 359 m := v.(map[string]interface{}) 360 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 361 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 362 buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) 363 buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["cidr"].(string)))) 364 buf.WriteString(fmt.Sprintf("%s-", m["from_group_id"].(string))) 365 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 366 367 return hashcode.String(buf.String()) 368 } 369 370 func SecGroupV2StateRefreshFunc(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { 371 return func() (interface{}, string, error) { 372 log.Printf("[DEBUG] Attempting to delete Security Group %s.\n", d.Id()) 373 374 err := secgroups.Delete(computeClient, d.Id()).ExtractErr() 375 if err != nil { 376 return nil, "", err 377 } 378 379 s, err := secgroups.Get(computeClient, d.Id()).Extract() 380 if err != nil { 381 err = CheckDeleted(d, err, "Security Group") 382 if err != nil { 383 return s, "", err 384 } else { 385 log.Printf("[DEBUG] Successfully deleted Security Group %s", d.Id()) 386 return s, "DELETED", nil 387 } 388 } 389 390 log.Printf("[DEBUG] Security Group %s still active.\n", d.Id()) 391 return s, "ACTIVE", nil 392 } 393 }