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