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