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  }