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