mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-13 19:01:09 -04:00
This commit enables security groups to be deleted in a safe way by checking their state over a period of time. This fixes occurrences when the API says the instance is deleted but it is still in the process of being deleted by OpenStack and thus the security group returns an error saying that there are still instances attached to the group.
332 lines
9.7 KiB
Go
332 lines
9.7 KiB
Go
package openstack
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
"github.com/rackspace/gophercloud"
|
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups"
|
|
)
|
|
|
|
func resourceComputeSecGroupV2() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceComputeSecGroupV2Create,
|
|
Read: resourceComputeSecGroupV2Read,
|
|
Update: resourceComputeSecGroupV2Update,
|
|
Delete: resourceComputeSecGroupV2Delete,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"region": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
DefaultFunc: envDefaultFuncAllowMissing("OS_REGION_NAME"),
|
|
},
|
|
"name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: false,
|
|
},
|
|
"description": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: false,
|
|
},
|
|
"rule": &schema.Schema{
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"id": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"from_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: false,
|
|
},
|
|
"to_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: false,
|
|
},
|
|
"ip_protocol": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: false,
|
|
},
|
|
"cidr": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: false,
|
|
},
|
|
"from_group_id": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: false,
|
|
},
|
|
"self": &schema.Schema{
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: false,
|
|
ForceNew: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) error {
|
|
config := meta.(*Config)
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
}
|
|
|
|
createOpts := secgroups.CreateOpts{
|
|
Name: d.Get("name").(string),
|
|
Description: d.Get("description").(string),
|
|
}
|
|
|
|
log.Printf("[DEBUG] Create Options: %#v", createOpts)
|
|
sg, err := secgroups.Create(computeClient, createOpts).Extract()
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating OpenStack security group: %s", err)
|
|
}
|
|
|
|
d.SetId(sg.ID)
|
|
|
|
createRuleOptsList := resourceSecGroupRulesV2(d)
|
|
for _, createRuleOpts := range createRuleOptsList {
|
|
_, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract()
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating OpenStack security group rule: %s", err)
|
|
}
|
|
}
|
|
|
|
return resourceComputeSecGroupV2Read(d, meta)
|
|
}
|
|
|
|
func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) error {
|
|
config := meta.(*Config)
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
}
|
|
|
|
sg, err := secgroups.Get(computeClient, d.Id()).Extract()
|
|
if err != nil {
|
|
return CheckDeleted(d, err, "security group")
|
|
}
|
|
|
|
d.Set("name", sg.Name)
|
|
d.Set("description", sg.Description)
|
|
rtm := rulesToMap(sg.Rules)
|
|
for _, v := range rtm {
|
|
if v["group"] == d.Get("name") {
|
|
v["self"] = "1"
|
|
} else {
|
|
v["self"] = "0"
|
|
}
|
|
}
|
|
log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm)
|
|
d.Set("rule", rtm)
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) error {
|
|
config := meta.(*Config)
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
}
|
|
|
|
updateOpts := secgroups.UpdateOpts{
|
|
Name: d.Get("name").(string),
|
|
Description: d.Get("description").(string),
|
|
}
|
|
|
|
log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts)
|
|
|
|
_, err = secgroups.Update(computeClient, d.Id(), updateOpts).Extract()
|
|
if err != nil {
|
|
return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err)
|
|
}
|
|
|
|
if d.HasChange("rule") {
|
|
oldSGRaw, newSGRaw := d.GetChange("rule")
|
|
oldSGRSlice, newSGRSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{})
|
|
oldSGRSet := schema.NewSet(secgroupRuleV2Hash, oldSGRSlice)
|
|
newSGRSet := schema.NewSet(secgroupRuleV2Hash, newSGRSlice)
|
|
secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet)
|
|
secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet)
|
|
|
|
log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd)
|
|
|
|
log.Printf("[DEBUG] Security groups rules to remove: %v", secgrouprulesToRemove)
|
|
|
|
for _, rawRule := range secgrouprulesToAdd.List() {
|
|
createRuleOpts := resourceSecGroupRuleCreateOptsV2(d, rawRule)
|
|
rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract()
|
|
if err != nil {
|
|
return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err)
|
|
}
|
|
log.Printf("[DEBUG] Added rule (%s) to OpenStack security group (%s) ", rule.ID, d.Id())
|
|
}
|
|
|
|
for _, r := range secgrouprulesToRemove.List() {
|
|
rule := resourceSecGroupRuleV2(d, r)
|
|
err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr()
|
|
if err != nil {
|
|
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
|
if !ok {
|
|
return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err)
|
|
}
|
|
if errCode.Actual == 404 {
|
|
continue
|
|
} else {
|
|
return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id())
|
|
}
|
|
} else {
|
|
log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return resourceComputeSecGroupV2Read(d, meta)
|
|
}
|
|
|
|
func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error {
|
|
config := meta.(*Config)
|
|
computeClient, err := config.computeV2Client(d.Get("region").(string))
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating OpenStack compute client: %s", err)
|
|
}
|
|
|
|
stateConf := &resource.StateChangeConf{
|
|
Pending: []string{"ACTIVE"},
|
|
Target: "DELETED",
|
|
Refresh: SecGroupV2StateRefreshFunc(computeClient, d),
|
|
Timeout: 10 * time.Minute,
|
|
Delay: 10 * time.Second,
|
|
MinTimeout: 3 * time.Second,
|
|
}
|
|
|
|
_, err = stateConf.WaitForState()
|
|
if err != nil {
|
|
return fmt.Errorf("Error deleting OpenStack security group: %s", err)
|
|
}
|
|
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
|
|
func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts {
|
|
rawRules := d.Get("rule").([]interface{})
|
|
createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules))
|
|
for i, raw := range rawRules {
|
|
rawMap := raw.(map[string]interface{})
|
|
groupId := rawMap["from_group_id"].(string)
|
|
if rawMap["self"].(bool) {
|
|
groupId = d.Id()
|
|
}
|
|
createRuleOptsList[i] = secgroups.CreateRuleOpts{
|
|
ParentGroupID: d.Id(),
|
|
FromPort: rawMap["from_port"].(int),
|
|
ToPort: rawMap["to_port"].(int),
|
|
IPProtocol: rawMap["ip_protocol"].(string),
|
|
CIDR: rawMap["cidr"].(string),
|
|
FromGroupID: groupId,
|
|
}
|
|
}
|
|
return createRuleOptsList
|
|
}
|
|
|
|
func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts {
|
|
rawMap := raw.(map[string]interface{})
|
|
groupId := rawMap["from_group_id"].(string)
|
|
if rawMap["self"].(bool) {
|
|
groupId = d.Id()
|
|
}
|
|
return secgroups.CreateRuleOpts{
|
|
ParentGroupID: d.Id(),
|
|
FromPort: rawMap["from_port"].(int),
|
|
ToPort: rawMap["to_port"].(int),
|
|
IPProtocol: rawMap["ip_protocol"].(string),
|
|
CIDR: rawMap["cidr"].(string),
|
|
FromGroupID: groupId,
|
|
}
|
|
}
|
|
|
|
func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.Rule {
|
|
rawMap := raw.(map[string]interface{})
|
|
return secgroups.Rule{
|
|
ID: rawMap["id"].(string),
|
|
ParentGroupID: d.Id(),
|
|
FromPort: rawMap["from_port"].(int),
|
|
ToPort: rawMap["to_port"].(int),
|
|
IPProtocol: rawMap["ip_protocol"].(string),
|
|
IPRange: secgroups.IPRange{CIDR: rawMap["cidr"].(string)},
|
|
}
|
|
}
|
|
|
|
func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} {
|
|
sgrMap := make([]map[string]interface{}, len(sgrs))
|
|
for i, sgr := range sgrs {
|
|
sgrMap[i] = map[string]interface{}{
|
|
"id": sgr.ID,
|
|
"from_port": sgr.FromPort,
|
|
"to_port": sgr.ToPort,
|
|
"ip_protocol": sgr.IPProtocol,
|
|
"cidr": sgr.IPRange.CIDR,
|
|
"group": sgr.Group.Name,
|
|
}
|
|
}
|
|
return sgrMap
|
|
}
|
|
|
|
func secgroupRuleV2Hash(v interface{}) int {
|
|
var buf bytes.Buffer
|
|
m := v.(map[string]interface{})
|
|
buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
|
|
buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
|
|
buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string)))
|
|
buf.WriteString(fmt.Sprintf("%s-", m["cidr"].(string)))
|
|
|
|
return hashcode.String(buf.String())
|
|
}
|
|
|
|
func SecGroupV2StateRefreshFunc(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc {
|
|
return func() (interface{}, string, error) {
|
|
log.Printf("[DEBUG] Attempting to delete Security Group %s.\n", d.Id())
|
|
|
|
err := secgroups.Delete(computeClient, d.Id()).ExtractErr()
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
s, err := secgroups.Get(computeClient, d.Id()).Extract()
|
|
if err != nil {
|
|
err = CheckDeleted(d, err, "Security Group")
|
|
if err != nil {
|
|
return s, "", err
|
|
} else {
|
|
log.Printf("[DEBUG] Successfully deleted Security Group %s", d.Id())
|
|
return s, "DELETED", nil
|
|
}
|
|
}
|
|
|
|
log.Printf("[DEBUG] Security Group %s still active.\n", d.Id())
|
|
return s, "ACTIVE", nil
|
|
}
|
|
}
|