github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/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  	_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: d.Id()})
   239  	if err != nil {
   240  		ec2err, ok := err.(*ec2.Error)
   241  		if ok && ec2err.Code == "InvalidGroup.NotFound" {
   242  			return nil
   243  		}
   244  	}
   245  
   246  	return err
   247  }
   248  
   249  func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   250  	p := meta.(*ResourceProvider)
   251  	ec2conn := p.ec2conn
   252  
   253  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   254  	if err != nil {
   255  		return err
   256  	}
   257  	if sgRaw == nil {
   258  		d.SetId("")
   259  		return nil
   260  	}
   261  
   262  	sg := sgRaw.(*ec2.SecurityGroupInfo)
   263  
   264  	// Gather our ingress rules
   265  	ingressRules := make([]map[string]interface{}, len(sg.IPPerms))
   266  	for i, perm := range sg.IPPerms {
   267  		n := make(map[string]interface{})
   268  		n["from_port"] = perm.FromPort
   269  		n["protocol"] = perm.Protocol
   270  		n["to_port"] = perm.ToPort
   271  
   272  		if len(perm.SourceIPs) > 0 {
   273  			n["cidr_blocks"] = perm.SourceIPs
   274  		}
   275  
   276  		var groups []string
   277  		if len(perm.SourceGroups) > 0 {
   278  			groups = flattenSecurityGroups(perm.SourceGroups)
   279  		}
   280  		for i, id := range groups {
   281  			if id == d.Id() {
   282  				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
   283  				n["self"] = true
   284  			}
   285  		}
   286  		if len(groups) > 0 {
   287  			n["security_groups"] = groups
   288  		}
   289  
   290  		ingressRules[i] = n
   291  	}
   292  
   293  	d.Set("description", sg.Description)
   294  	d.Set("name", sg.Name)
   295  	d.Set("vpc_id", sg.VpcId)
   296  	d.Set("owner_id", sg.OwnerId)
   297  	d.Set("ingress", ingressRules)
   298  
   299  	return nil
   300  }
   301  
   302  // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   303  // a security group.
   304  func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   305  	return func() (interface{}, string, error) {
   306  		sgs := []ec2.SecurityGroup{ec2.SecurityGroup{Id: id}}
   307  		resp, err := conn.SecurityGroups(sgs, nil)
   308  		if err != nil {
   309  			if ec2err, ok := err.(*ec2.Error); ok {
   310  				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
   311  					ec2err.Code == "InvalidGroup.NotFound" {
   312  					resp = nil
   313  					err = nil
   314  				}
   315  			}
   316  
   317  			if err != nil {
   318  				log.Printf("Error on SGStateRefresh: %s", err)
   319  				return nil, "", err
   320  			}
   321  		}
   322  
   323  		if resp == nil {
   324  			return nil, "", nil
   325  		}
   326  
   327  		group := &resp.Groups[0]
   328  		return group, "exists", nil
   329  	}
   330  }