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