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