github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/builtin/providers/aws/resource_aws_security_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     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/mitchellh/goamz/ec2"
    14  )
    15  
    16  func resourceAwsSecurityGroup() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsSecurityGroupCreate,
    19  		Read:   resourceAwsSecurityGroupRead,
    20  		Update: resourceAwsSecurityGroupUpdate,
    21  		Delete: resourceAwsSecurityGroupDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"name": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"description": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  			},
    34  
    35  			"vpc_id": &schema.Schema{
    36  				Type:     schema.TypeString,
    37  				Optional: true,
    38  				ForceNew: true,
    39  				Computed: true,
    40  			},
    41  
    42  			"ingress": &schema.Schema{
    43  				Type:     schema.TypeSet,
    44  				Optional: true,
    45  				Elem: &schema.Resource{
    46  					Schema: map[string]*schema.Schema{
    47  						"from_port": &schema.Schema{
    48  							Type:     schema.TypeInt,
    49  							Required: true,
    50  						},
    51  
    52  						"to_port": &schema.Schema{
    53  							Type:     schema.TypeInt,
    54  							Required: true,
    55  						},
    56  
    57  						"protocol": &schema.Schema{
    58  							Type:     schema.TypeString,
    59  							Required: true,
    60  						},
    61  
    62  						"cidr_blocks": &schema.Schema{
    63  							Type:     schema.TypeList,
    64  							Optional: true,
    65  							Elem:     &schema.Schema{Type: schema.TypeString},
    66  						},
    67  
    68  						"security_groups": &schema.Schema{
    69  							Type:     schema.TypeSet,
    70  							Optional: true,
    71  							Elem:     &schema.Schema{Type: schema.TypeString},
    72  							Set: func(v interface{}) int {
    73  								return hashcode.String(v.(string))
    74  							},
    75  						},
    76  
    77  						"self": &schema.Schema{
    78  							Type:     schema.TypeBool,
    79  							Optional: true,
    80  							Default:  false,
    81  						},
    82  					},
    83  				},
    84  				Set: resourceAwsSecurityGroupRuleHash,
    85  			},
    86  
    87  			"egress": &schema.Schema{
    88  				Type:     schema.TypeSet,
    89  				Optional: true,
    90  				Computed: true,
    91  				Elem: &schema.Resource{
    92  					Schema: map[string]*schema.Schema{
    93  						"from_port": &schema.Schema{
    94  							Type:     schema.TypeInt,
    95  							Required: true,
    96  						},
    97  
    98  						"to_port": &schema.Schema{
    99  							Type:     schema.TypeInt,
   100  							Required: true,
   101  						},
   102  
   103  						"protocol": &schema.Schema{
   104  							Type:     schema.TypeString,
   105  							Required: true,
   106  						},
   107  
   108  						"cidr_blocks": &schema.Schema{
   109  							Type:     schema.TypeList,
   110  							Optional: true,
   111  							Elem:     &schema.Schema{Type: schema.TypeString},
   112  						},
   113  
   114  						"security_groups": &schema.Schema{
   115  							Type:     schema.TypeSet,
   116  							Optional: true,
   117  							Elem:     &schema.Schema{Type: schema.TypeString},
   118  							Set: func(v interface{}) int {
   119  								return hashcode.String(v.(string))
   120  							},
   121  						},
   122  
   123  						"self": &schema.Schema{
   124  							Type:     schema.TypeBool,
   125  							Optional: true,
   126  							Default:  false,
   127  						},
   128  					},
   129  				},
   130  				Set: resourceAwsSecurityGroupRuleHash,
   131  			},
   132  
   133  			"owner_id": &schema.Schema{
   134  				Type:     schema.TypeString,
   135  				Computed: true,
   136  			},
   137  
   138  			"tags": tagsSchema(),
   139  		},
   140  	}
   141  }
   142  
   143  func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
   144  	ec2conn := meta.(*AWSClient).ec2conn
   145  
   146  	securityGroupOpts := ec2.SecurityGroup{
   147  		Name: d.Get("name").(string),
   148  	}
   149  
   150  	if v := d.Get("vpc_id"); v != nil {
   151  		securityGroupOpts.VpcId = v.(string)
   152  	}
   153  
   154  	if v := d.Get("description"); v != nil {
   155  		securityGroupOpts.Description = v.(string)
   156  	}
   157  
   158  	log.Printf(
   159  		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
   160  	createResp, err := ec2conn.CreateSecurityGroup(securityGroupOpts)
   161  	if err != nil {
   162  		return fmt.Errorf("Error creating Security Group: %s", err)
   163  	}
   164  
   165  	d.SetId(createResp.Id)
   166  
   167  	log.Printf("[INFO] Security Group ID: %s", d.Id())
   168  
   169  	// Wait for the security group to truly exist
   170  	log.Printf(
   171  		"[DEBUG] Waiting for Security Group (%s) to exist",
   172  		d.Id())
   173  	stateConf := &resource.StateChangeConf{
   174  		Pending: []string{""},
   175  		Target:  "exists",
   176  		Refresh: SGStateRefreshFunc(ec2conn, d.Id()),
   177  		Timeout: 1 * time.Minute,
   178  	}
   179  	if _, err := stateConf.WaitForState(); err != nil {
   180  		return fmt.Errorf(
   181  			"Error waiting for Security Group (%s) to become available: %s",
   182  			d.Id(), err)
   183  	}
   184  
   185  	return resourceAwsSecurityGroupUpdate(d, meta)
   186  }
   187  
   188  func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   189  	ec2conn := meta.(*AWSClient).ec2conn
   190  
   191  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   192  	if err != nil {
   193  		return err
   194  	}
   195  	if sgRaw == nil {
   196  		d.SetId("")
   197  		return nil
   198  	}
   199  
   200  	sg := sgRaw.(*ec2.SecurityGroupInfo)
   201  
   202  	ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPerms)
   203  	egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermsEgress)
   204  
   205  	d.Set("description", sg.Description)
   206  	d.Set("name", sg.Name)
   207  	d.Set("vpc_id", sg.VpcId)
   208  	d.Set("owner_id", sg.OwnerId)
   209  	d.Set("ingress", ingressRules)
   210  	d.Set("egress", egressRules)
   211  	d.Set("tags", tagsToMap(sg.Tags))
   212  
   213  	return nil
   214  }
   215  
   216  func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   217  	ec2conn := meta.(*AWSClient).ec2conn
   218  
   219  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   220  	if err != nil {
   221  		return err
   222  	}
   223  	if sgRaw == nil {
   224  		d.SetId("")
   225  		return nil
   226  	}
   227  	group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup
   228  
   229  	err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	if d.Get("vpc_id") != nil {
   235  		err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
   236  		if err != nil {
   237  			return err
   238  		}
   239  	}
   240  
   241  	if err := setTags(ec2conn, d); err != nil {
   242  		return err
   243  	}
   244  
   245  	d.SetPartial("tags")
   246  
   247  	return resourceAwsSecurityGroupRead(d, meta)
   248  }
   249  
   250  func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   251  	ec2conn := meta.(*AWSClient).ec2conn
   252  
   253  	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
   254  
   255  	return resource.Retry(5*time.Minute, func() error {
   256  		_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()})
   257  		if err != nil {
   258  			ec2err, ok := err.(*ec2.Error)
   259  			if !ok {
   260  				return err
   261  			}
   262  
   263  			switch ec2err.Code {
   264  			case "InvalidGroup.NotFound":
   265  				return nil
   266  			case "DependencyViolation":
   267  				// If it is a dependency violation, we want to retry
   268  				return err
   269  			default:
   270  				// Any other error, we want to quit the retry loop immediately
   271  				return resource.RetryError{Err: err}
   272  			}
   273  		}
   274  
   275  		return nil
   276  	})
   277  }
   278  
   279  func resourceAwsSecurityGroupRuleHash(v interface{}) int {
   280  	var buf bytes.Buffer
   281  	m := v.(map[string]interface{})
   282  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   283  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   284  	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
   285  
   286  	// We need to make sure to sort the strings below so that we always
   287  	// generate the same hash code no matter what is in the set.
   288  	if v, ok := m["cidr_blocks"]; ok {
   289  		vs := v.([]interface{})
   290  		s := make([]string, len(vs))
   291  		for i, raw := range vs {
   292  			s[i] = raw.(string)
   293  		}
   294  		sort.Strings(s)
   295  
   296  		for _, v := range s {
   297  			buf.WriteString(fmt.Sprintf("%s-", v))
   298  		}
   299  	}
   300  	if v, ok := m["security_groups"]; ok {
   301  		vs := v.(*schema.Set).List()
   302  		s := make([]string, len(vs))
   303  		for i, raw := range vs {
   304  			s[i] = raw.(string)
   305  		}
   306  		sort.Strings(s)
   307  
   308  		for _, v := range s {
   309  			buf.WriteString(fmt.Sprintf("%s-", v))
   310  		}
   311  	}
   312  
   313  	return hashcode.String(buf.String())
   314  }
   315  
   316  func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPerm) []map[string]interface{} {
   317  	ruleMap := make(map[string]map[string]interface{})
   318  	for _, perm := range permissions {
   319  		k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort)
   320  		m, ok := ruleMap[k]
   321  		if !ok {
   322  			m = make(map[string]interface{})
   323  			ruleMap[k] = m
   324  		}
   325  
   326  		m["from_port"] = perm.FromPort
   327  		m["to_port"] = perm.ToPort
   328  		m["protocol"] = perm.Protocol
   329  
   330  		if len(perm.SourceIPs) > 0 {
   331  			raw, ok := m["cidr_blocks"]
   332  			if !ok {
   333  				raw = make([]string, 0, len(perm.SourceIPs))
   334  			}
   335  			list := raw.([]string)
   336  
   337  			list = append(list, perm.SourceIPs...)
   338  			m["cidr_blocks"] = list
   339  		}
   340  
   341  		var groups []string
   342  		if len(perm.SourceGroups) > 0 {
   343  			groups = flattenSecurityGroups(perm.SourceGroups)
   344  		}
   345  		for i, id := range groups {
   346  			if id == d.Id() {
   347  				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
   348  				m["self"] = true
   349  			}
   350  		}
   351  
   352  		if len(groups) > 0 {
   353  			raw, ok := m["security_groups"]
   354  			if !ok {
   355  				raw = make([]string, 0, len(groups))
   356  			}
   357  			list := raw.([]string)
   358  
   359  			list = append(list, groups...)
   360  			m["security_groups"] = list
   361  		}
   362  	}
   363  	rules := make([]map[string]interface{}, 0, len(ruleMap))
   364  	for _, m := range ruleMap {
   365  		rules = append(rules, m)
   366  	}
   367  
   368  	return rules
   369  }
   370  
   371  func resourceAwsSecurityGroupUpdateRules(
   372  	d *schema.ResourceData, ruleset string,
   373  	meta interface{}, group ec2.SecurityGroup) error {
   374  	if d.HasChange(ruleset) {
   375  		o, n := d.GetChange(ruleset)
   376  		if o == nil {
   377  			o = new(schema.Set)
   378  		}
   379  		if n == nil {
   380  			n = new(schema.Set)
   381  		}
   382  
   383  		os := o.(*schema.Set)
   384  		ns := n.(*schema.Set)
   385  
   386  		remove := expandIPPerms(d.Id(), os.Difference(ns).List())
   387  		add := expandIPPerms(d.Id(), ns.Difference(os).List())
   388  
   389  		// TODO: We need to handle partial state better in the in-between
   390  		// in this update.
   391  
   392  		// TODO: It'd be nicer to authorize before removing, but then we have
   393  		// to deal with complicated unrolling to get individual CIDR blocks
   394  		// to avoid authorizing already authorized sources. Removing before
   395  		// adding is easier here, and Terraform should be fast enough to
   396  		// not have service issues.
   397  
   398  		if len(remove) > 0 || len(add) > 0 {
   399  			ec2conn := meta.(*AWSClient).ec2conn
   400  
   401  			if len(remove) > 0 {
   402  				// Revoke the old rules
   403  				revoke := ec2conn.RevokeSecurityGroup
   404  				if ruleset == "egress" {
   405  					revoke = ec2conn.RevokeSecurityGroupEgress
   406  				}
   407  
   408  				log.Printf("[DEBUG] Revoking security group %s %s rule: %#v",
   409  					group, ruleset, remove)
   410  				if _, err := revoke(group, remove); err != nil {
   411  					return fmt.Errorf(
   412  						"Error revoking security group %s rules: %s",
   413  						ruleset, err)
   414  				}
   415  			}
   416  
   417  			if len(add) > 0 {
   418  				// Authorize the new rules
   419  				authorize := ec2conn.AuthorizeSecurityGroup
   420  				if ruleset == "egress" {
   421  					authorize = ec2conn.AuthorizeSecurityGroupEgress
   422  				}
   423  
   424  				log.Printf("[DEBUG] Authorizing security group %s %s rule: %#v",
   425  					group, ruleset, add)
   426  				if _, err := authorize(group, add); err != nil {
   427  					return fmt.Errorf(
   428  						"Error authorizing security group %s rules: %s",
   429  						ruleset, err)
   430  				}
   431  			}
   432  		}
   433  	}
   434  
   435  	return nil
   436  }
   437  
   438  // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   439  // a security group.
   440  func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   441  	return func() (interface{}, string, error) {
   442  		sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
   443  		resp, err := conn.SecurityGroups(sgs, nil)
   444  		if err != nil {
   445  			if ec2err, ok := err.(*ec2.Error); ok {
   446  				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
   447  					ec2err.Code == "InvalidGroup.NotFound" {
   448  					resp = nil
   449  					err = nil
   450  				}
   451  			}
   452  
   453  			if err != nil {
   454  				log.Printf("Error on SGStateRefresh: %s", err)
   455  				return nil, "", err
   456  			}
   457  		}
   458  
   459  		if resp == nil {
   460  			return nil, "", nil
   461  		}
   462  
   463  		group := &resp.Groups[0]
   464  		return group, "exists", nil
   465  	}
   466  }