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