github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/builtin/providers/aws/resource_aws_network_acl.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/awslabs/aws-sdk-go/aws"
    10  	"github.com/awslabs/aws-sdk-go/service/ec2"
    11  	"github.com/hashicorp/terraform/helper/hashcode"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceAwsNetworkAcl() *schema.Resource {
    17  
    18  	return &schema.Resource{
    19  		Create: resourceAwsNetworkAclCreate,
    20  		Read:   resourceAwsNetworkAclRead,
    21  		Delete: resourceAwsNetworkAclDelete,
    22  		Update: resourceAwsNetworkAclUpdate,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"vpc_id": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Required: true,
    28  				ForceNew: true,
    29  				Computed: false,
    30  			},
    31  			"subnet_id": &schema.Schema{
    32  				Type:     schema.TypeString,
    33  				Optional: true,
    34  				ForceNew: true,
    35  				Computed: false,
    36  			},
    37  			"ingress": &schema.Schema{
    38  				Type:     schema.TypeSet,
    39  				Required: false,
    40  				Optional: true,
    41  				Elem: &schema.Resource{
    42  					Schema: map[string]*schema.Schema{
    43  						"from_port": &schema.Schema{
    44  							Type:     schema.TypeInt,
    45  							Required: true,
    46  						},
    47  						"to_port": &schema.Schema{
    48  							Type:     schema.TypeInt,
    49  							Required: true,
    50  						},
    51  						"rule_no": &schema.Schema{
    52  							Type:     schema.TypeInt,
    53  							Required: true,
    54  						},
    55  						"action": &schema.Schema{
    56  							Type:     schema.TypeString,
    57  							Required: true,
    58  						},
    59  						"protocol": &schema.Schema{
    60  							Type:     schema.TypeString,
    61  							Required: true,
    62  						},
    63  						"cidr_block": &schema.Schema{
    64  							Type:     schema.TypeString,
    65  							Optional: true,
    66  						},
    67  					},
    68  				},
    69  				Set: resourceAwsNetworkAclEntryHash,
    70  			},
    71  			"egress": &schema.Schema{
    72  				Type:     schema.TypeSet,
    73  				Required: false,
    74  				Optional: true,
    75  				Elem: &schema.Resource{
    76  					Schema: map[string]*schema.Schema{
    77  						"from_port": &schema.Schema{
    78  							Type:     schema.TypeInt,
    79  							Required: true,
    80  						},
    81  						"to_port": &schema.Schema{
    82  							Type:     schema.TypeInt,
    83  							Required: true,
    84  						},
    85  						"rule_no": &schema.Schema{
    86  							Type:     schema.TypeInt,
    87  							Required: true,
    88  						},
    89  						"action": &schema.Schema{
    90  							Type:     schema.TypeString,
    91  							Required: true,
    92  						},
    93  						"protocol": &schema.Schema{
    94  							Type:     schema.TypeString,
    95  							Required: true,
    96  						},
    97  						"cidr_block": &schema.Schema{
    98  							Type:     schema.TypeString,
    99  							Optional: true,
   100  						},
   101  					},
   102  				},
   103  				Set: resourceAwsNetworkAclEntryHash,
   104  			},
   105  			"tags": tagsSchema(),
   106  		},
   107  	}
   108  }
   109  
   110  func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
   111  
   112  	conn := meta.(*AWSClient).ec2conn
   113  
   114  	// Create the Network Acl
   115  	createOpts := &ec2.CreateNetworkACLInput{
   116  		VPCID: aws.String(d.Get("vpc_id").(string)),
   117  	}
   118  
   119  	log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
   120  	resp, err := conn.CreateNetworkACL(createOpts)
   121  	if err != nil {
   122  		return fmt.Errorf("Error creating network acl: %s", err)
   123  	}
   124  
   125  	// Get the ID and store it
   126  	networkAcl := resp.NetworkACL
   127  	d.SetId(*networkAcl.NetworkACLID)
   128  	log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkACLID)
   129  
   130  	// Update rules and subnet association once acl is created
   131  	return resourceAwsNetworkAclUpdate(d, meta)
   132  }
   133  
   134  func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
   135  	conn := meta.(*AWSClient).ec2conn
   136  
   137  	resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{
   138  		NetworkACLIDs: []*string{aws.String(d.Id())},
   139  	})
   140  
   141  	if err != nil {
   142  		return err
   143  	}
   144  	if resp == nil {
   145  		return nil
   146  	}
   147  
   148  	networkAcl := resp.NetworkACLs[0]
   149  	var ingressEntries []*ec2.NetworkACLEntry
   150  	var egressEntries []*ec2.NetworkACLEntry
   151  
   152  	// separate the ingress and egress rules
   153  	for _, e := range networkAcl.Entries {
   154  		if *e.Egress == true {
   155  			egressEntries = append(egressEntries, e)
   156  		} else {
   157  			ingressEntries = append(ingressEntries, e)
   158  		}
   159  	}
   160  
   161  	d.Set("vpc_id", networkAcl.VPCID)
   162  	d.Set("ingress", ingressEntries)
   163  	d.Set("egress", egressEntries)
   164  	d.Set("tags", tagsToMapSDK(networkAcl.Tags))
   165  
   166  	return nil
   167  }
   168  
   169  func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
   170  	conn := meta.(*AWSClient).ec2conn
   171  	d.Partial(true)
   172  
   173  	if d.HasChange("ingress") {
   174  		err := updateNetworkAclEntries(d, "ingress", conn)
   175  		if err != nil {
   176  			return err
   177  		}
   178  	}
   179  
   180  	if d.HasChange("egress") {
   181  		err := updateNetworkAclEntries(d, "egress", conn)
   182  		if err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	if d.HasChange("subnet_id") {
   188  		//associate new subnet with the acl.
   189  		_, n := d.GetChange("subnet_id")
   190  		newSubnet := n.(string)
   191  		association, err := findNetworkAclAssociation(newSubnet, conn)
   192  		if err != nil {
   193  			return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
   194  		}
   195  		_, err = conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationInput{
   196  			AssociationID: association.NetworkACLAssociationID,
   197  			NetworkACLID:  aws.String(d.Id()),
   198  		})
   199  		if err != nil {
   200  			return err
   201  		}
   202  	}
   203  
   204  	if err := setTagsSDK(conn, d); err != nil {
   205  		return err
   206  	} else {
   207  		d.SetPartial("tags")
   208  	}
   209  
   210  	d.Partial(false)
   211  	return resourceAwsNetworkAclRead(d, meta)
   212  }
   213  
   214  func updateNetworkAclEntries(d *schema.ResourceData, entryType string, conn *ec2.EC2) error {
   215  
   216  	o, n := d.GetChange(entryType)
   217  
   218  	if o == nil {
   219  		o = new(schema.Set)
   220  	}
   221  	if n == nil {
   222  		n = new(schema.Set)
   223  	}
   224  
   225  	os := o.(*schema.Set)
   226  	ns := n.(*schema.Set)
   227  
   228  	toBeDeleted, err := expandNetworkAclEntries(os.Difference(ns).List(), entryType)
   229  	if err != nil {
   230  		return err
   231  	}
   232  	for _, remove := range toBeDeleted {
   233  		// Delete old Acl
   234  		_, err := conn.DeleteNetworkACLEntry(&ec2.DeleteNetworkACLEntryInput{
   235  			NetworkACLID: aws.String(d.Id()),
   236  			RuleNumber:   remove.RuleNumber,
   237  			Egress:       remove.Egress,
   238  		})
   239  		if err != nil {
   240  			return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
   241  		}
   242  	}
   243  
   244  	toBeCreated, err := expandNetworkAclEntries(ns.Difference(os).List(), entryType)
   245  	if err != nil {
   246  		return err
   247  	}
   248  	for _, add := range toBeCreated {
   249  		// Add new Acl entry
   250  		_, err := conn.CreateNetworkACLEntry(&ec2.CreateNetworkACLEntryInput{
   251  			NetworkACLID: aws.String(d.Id()),
   252  			CIDRBlock:    add.CIDRBlock,
   253  			Egress:       add.Egress,
   254  			PortRange:    add.PortRange,
   255  			Protocol:     add.Protocol,
   256  			RuleAction:   add.RuleAction,
   257  			RuleNumber:   add.RuleNumber,
   258  		})
   259  		if err != nil {
   260  			return fmt.Errorf("Error creating %s entry: %s", entryType, err)
   261  		}
   262  	}
   263  	return nil
   264  }
   265  
   266  func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
   267  	conn := meta.(*AWSClient).ec2conn
   268  
   269  	log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
   270  	return resource.Retry(5*time.Minute, func() error {
   271  		_, err := conn.DeleteNetworkACL(&ec2.DeleteNetworkACLInput{
   272  			NetworkACLID: aws.String(d.Id()),
   273  		})
   274  		if err != nil {
   275  			ec2err := err.(aws.APIError)
   276  			switch ec2err.Code {
   277  			case "InvalidNetworkAclID.NotFound":
   278  				return nil
   279  			case "DependencyViolation":
   280  				// In case of dependency violation, we remove the association between subnet and network acl.
   281  				// This means the subnet is attached to default acl of vpc.
   282  				association, err := findNetworkAclAssociation(d.Get("subnet_id").(string), conn)
   283  				if err != nil {
   284  					return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
   285  				}
   286  				defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn)
   287  				if err != nil {
   288  					return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err)
   289  				}
   290  				_, err = conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationInput{
   291  					AssociationID: association.NetworkACLAssociationID,
   292  					NetworkACLID:  defaultAcl.NetworkACLID,
   293  				})
   294  				return resource.RetryError{Err: err}
   295  			default:
   296  				// Any other error, we want to quit the retry loop immediately
   297  				return resource.RetryError{Err: err}
   298  			}
   299  		}
   300  		log.Printf("[Info] Deleted network ACL %s successfully", d.Id())
   301  		return nil
   302  	})
   303  }
   304  
   305  func resourceAwsNetworkAclEntryHash(v interface{}) int {
   306  	var buf bytes.Buffer
   307  	m := v.(map[string]interface{})
   308  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   309  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   310  	buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int)))
   311  	buf.WriteString(fmt.Sprintf("%s-", m["action"].(string)))
   312  	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
   313  	buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string)))
   314  
   315  	if v, ok := m["ssl_certificate_id"]; ok {
   316  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   317  	}
   318  
   319  	return hashcode.String(buf.String())
   320  }
   321  
   322  func getDefaultNetworkAcl(vpc_id string, conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) {
   323  	resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{
   324  		NetworkACLIDs: []*string{},
   325  		Filters: []*ec2.Filter{
   326  			&ec2.Filter{
   327  				Name:   aws.String("default"),
   328  				Values: []*string{aws.String("true")},
   329  			},
   330  			&ec2.Filter{
   331  				Name:   aws.String("vpc-id"),
   332  				Values: []*string{aws.String(vpc_id)},
   333  			},
   334  		},
   335  	})
   336  
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	return resp.NetworkACLs[0], nil
   341  }
   342  
   343  func findNetworkAclAssociation(subnetId string, conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) {
   344  	resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{
   345  		Filters: []*ec2.Filter{
   346  			&ec2.Filter{
   347  				Name:   aws.String("association.subnet-id"),
   348  				Values: []*string{aws.String(subnetId)},
   349  			},
   350  		},
   351  	})
   352  
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  	if resp.NetworkACLs != nil && len(resp.NetworkACLs) > 0 {
   357  		for _, association := range resp.NetworkACLs[0].Associations {
   358  			if *association.SubnetID == subnetId {
   359  				return association, nil
   360  			}
   361  		}
   362  	}
   363  	return nil, fmt.Errorf("could not find association for subnet %s ", subnetId)
   364  }