github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/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: resourceAwsSecurityGroupIngressHash,
    85  			},
    86  
    87  			"owner_id": &schema.Schema{
    88  				Type:     schema.TypeString,
    89  				Computed: true,
    90  			},
    91  
    92  			"tags": tagsSchema(),
    93  		},
    94  	}
    95  }
    96  
    97  func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
    98  	ec2conn := meta.(*AWSClient).ec2conn
    99  
   100  	securityGroupOpts := ec2.SecurityGroup{
   101  		Name: d.Get("name").(string),
   102  	}
   103  
   104  	if v := d.Get("vpc_id"); v != nil {
   105  		securityGroupOpts.VpcId = v.(string)
   106  	}
   107  
   108  	if v := d.Get("description"); v != nil {
   109  		securityGroupOpts.Description = v.(string)
   110  	}
   111  
   112  	log.Printf(
   113  		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
   114  	createResp, err := ec2conn.CreateSecurityGroup(securityGroupOpts)
   115  	if err != nil {
   116  		return fmt.Errorf("Error creating Security Group: %s", err)
   117  	}
   118  
   119  	d.SetId(createResp.Id)
   120  
   121  	log.Printf("[INFO] Security Group ID: %s", d.Id())
   122  
   123  	// Wait for the security group to truly exist
   124  	log.Printf(
   125  		"[DEBUG] Waiting for Security Group (%s) to exist",
   126  		d.Id())
   127  	stateConf := &resource.StateChangeConf{
   128  		Pending: []string{""},
   129  		Target:  "exists",
   130  		Refresh: SGStateRefreshFunc(ec2conn, d.Id()),
   131  		Timeout: 1 * time.Minute,
   132  	}
   133  	if _, err := stateConf.WaitForState(); err != nil {
   134  		return fmt.Errorf(
   135  			"Error waiting for Security Group (%s) to become available: %s",
   136  			d.Id(), err)
   137  	}
   138  
   139  	return resourceAwsSecurityGroupUpdate(d, meta)
   140  }
   141  
   142  func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   143  	ec2conn := meta.(*AWSClient).ec2conn
   144  
   145  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   146  	if err != nil {
   147  		return err
   148  	}
   149  	if sgRaw == nil {
   150  		d.SetId("")
   151  		return nil
   152  	}
   153  
   154  	sg := sgRaw.(*ec2.SecurityGroupInfo)
   155  
   156  	// Gather our ingress rules
   157  	ingressMap := make(map[string]map[string]interface{})
   158  	for _, perm := range sg.IPPerms {
   159  		k := fmt.Sprintf("%s-%d-%d", perm.Protocol, perm.FromPort, perm.ToPort)
   160  		m, ok := ingressMap[k]
   161  		if !ok {
   162  			m = make(map[string]interface{})
   163  			ingressMap[k] = m
   164  		}
   165  
   166  		m["from_port"] = perm.FromPort
   167  		m["to_port"] = perm.ToPort
   168  		m["protocol"] = perm.Protocol
   169  
   170  		if len(perm.SourceIPs) > 0 {
   171  			raw, ok := m["cidr_blocks"]
   172  			if !ok {
   173  				raw = make([]string, 0, len(perm.SourceIPs))
   174  			}
   175  			list := raw.([]string)
   176  
   177  			list = append(list, perm.SourceIPs...)
   178  			m["cidr_blocks"] = list
   179  		}
   180  
   181  		var groups []string
   182  		if len(perm.SourceGroups) > 0 {
   183  			groups = flattenSecurityGroups(perm.SourceGroups)
   184  		}
   185  		for i, id := range groups {
   186  			if id == d.Id() {
   187  				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
   188  				m["self"] = true
   189  			}
   190  		}
   191  
   192  		if len(groups) > 0 {
   193  			raw, ok := m["security_groups"]
   194  			if !ok {
   195  				raw = make([]string, 0, len(groups))
   196  			}
   197  			list := raw.([]string)
   198  
   199  			list = append(list, groups...)
   200  			m["security_groups"] = list
   201  		}
   202  	}
   203  	ingressRules := make([]map[string]interface{}, 0, len(ingressMap))
   204  	for _, m := range ingressMap {
   205  		ingressRules = append(ingressRules, m)
   206  	}
   207  
   208  	d.Set("description", sg.Description)
   209  	d.Set("name", sg.Name)
   210  	d.Set("vpc_id", sg.VpcId)
   211  	d.Set("owner_id", sg.OwnerId)
   212  	d.Set("ingress", ingressRules)
   213  	d.Set("tags", tagsToMap(sg.Tags))
   214  
   215  	return nil
   216  }
   217  
   218  func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   219  	ec2conn := meta.(*AWSClient).ec2conn
   220  
   221  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   222  	if err != nil {
   223  		return err
   224  	}
   225  	if sgRaw == nil {
   226  		d.SetId("")
   227  		return nil
   228  	}
   229  	group := sgRaw.(*ec2.SecurityGroupInfo).SecurityGroup
   230  
   231  	if d.HasChange("ingress") {
   232  		o, n := d.GetChange("ingress")
   233  		if o == nil {
   234  			o = new(schema.Set)
   235  		}
   236  		if n == nil {
   237  			n = new(schema.Set)
   238  		}
   239  
   240  		os := o.(*schema.Set)
   241  		ns := n.(*schema.Set)
   242  
   243  		remove := expandIPPerms(d.Id(), os.Difference(ns).List())
   244  		add := expandIPPerms(d.Id(), ns.Difference(os).List())
   245  
   246  		// TODO: We need to handle partial state better in the in-between
   247  		// in this update.
   248  
   249  		// TODO: It'd be nicer to authorize before removing, but then we have
   250  		// to deal with complicated unrolling to get individual CIDR blocks
   251  		// to avoid authorizing already authorized sources. Removing before
   252  		// adding is easier here, and Terraform should be fast enough to
   253  		// not have service issues.
   254  
   255  		if len(remove) > 0 {
   256  			// Revoke the old rules
   257  			_, err = ec2conn.RevokeSecurityGroup(group, remove)
   258  			if err != nil {
   259  				return fmt.Errorf("Error authorizing security group ingress rules: %s", err)
   260  			}
   261  		}
   262  
   263  		if len(add) > 0 {
   264  			// Authorize the new rules
   265  			_, err := ec2conn.AuthorizeSecurityGroup(group, add)
   266  			if err != nil {
   267  				return fmt.Errorf("Error authorizing security group ingress rules: %s", err)
   268  			}
   269  		}
   270  	}
   271  
   272  	if err := setTags(ec2conn, d); err != nil {
   273  		return err
   274  	} else {
   275  		d.SetPartial("tags")
   276  	}
   277  
   278  	return resourceAwsSecurityGroupRead(d, meta)
   279  }
   280  
   281  func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   282  	ec2conn := meta.(*AWSClient).ec2conn
   283  
   284  	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
   285  
   286  	return resource.Retry(5*time.Minute, func() error {
   287  		_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()})
   288  		if err != nil {
   289  			ec2err, ok := err.(*ec2.Error)
   290  			if !ok {
   291  				return err
   292  			}
   293  
   294  			switch ec2err.Code {
   295  			case "InvalidGroup.NotFound":
   296  				return nil
   297  			case "DependencyViolation":
   298  				// If it is a dependency violation, we want to retry
   299  				return err
   300  			default:
   301  				// Any other error, we want to quit the retry loop immediately
   302  				return resource.RetryError{err}
   303  			}
   304  		}
   305  
   306  		return nil
   307  	})
   308  }
   309  
   310  func resourceAwsSecurityGroupIngressHash(v interface{}) int {
   311  	var buf bytes.Buffer
   312  	m := v.(map[string]interface{})
   313  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   314  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   315  	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
   316  
   317  	// We need to make sure to sort the strings below so that we always
   318  	// generate the same hash code no matter what is in the set.
   319  	if v, ok := m["cidr_blocks"]; ok {
   320  		vs := v.([]interface{})
   321  		s := make([]string, len(vs))
   322  		for i, raw := range vs {
   323  			s[i] = raw.(string)
   324  		}
   325  		sort.Strings(s)
   326  
   327  		for _, v := range s {
   328  			buf.WriteString(fmt.Sprintf("%s-", v))
   329  		}
   330  	}
   331  	if v, ok := m["security_groups"]; ok {
   332  		vs := v.(*schema.Set).List()
   333  		s := make([]string, len(vs))
   334  		for i, raw := range vs {
   335  			s[i] = raw.(string)
   336  		}
   337  		sort.Strings(s)
   338  
   339  		for _, v := range s {
   340  			buf.WriteString(fmt.Sprintf("%s-", v))
   341  		}
   342  	}
   343  
   344  	return hashcode.String(buf.String())
   345  }
   346  
   347  // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   348  // a security group.
   349  func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   350  	return func() (interface{}, string, error) {
   351  		sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
   352  		resp, err := conn.SecurityGroups(sgs, nil)
   353  		if err != nil {
   354  			if ec2err, ok := err.(*ec2.Error); ok {
   355  				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
   356  					ec2err.Code == "InvalidGroup.NotFound" {
   357  					resp = nil
   358  					err = nil
   359  				}
   360  			}
   361  
   362  			if err != nil {
   363  				log.Printf("Error on SGStateRefresh: %s", err)
   364  				return nil, "", err
   365  			}
   366  		}
   367  
   368  		if resp == nil {
   369  			return nil, "", nil
   370  		}
   371  
   372  		group := &resp.Groups[0]
   373  		return group, "exists", nil
   374  	}
   375  }