github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go (about)

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