github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_network_acl_rule.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsNetworkAclRule() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsNetworkAclRuleCreate,
    20  		Read:   resourceAwsNetworkAclRuleRead,
    21  		Delete: resourceAwsNetworkAclRuleDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"network_acl_id": {
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  			"rule_number": {
    30  				Type:     schema.TypeInt,
    31  				Required: true,
    32  				ForceNew: true,
    33  			},
    34  			"egress": {
    35  				Type:     schema.TypeBool,
    36  				Optional: true,
    37  				ForceNew: true,
    38  				Default:  false,
    39  			},
    40  			"protocol": {
    41  				Type:     schema.TypeString,
    42  				Required: true,
    43  				ForceNew: true,
    44  				DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
    45  					if old == "all" && new == "-1" || old == "-1" && new == "all" {
    46  						return true
    47  					}
    48  					return false
    49  				},
    50  			},
    51  			"rule_action": {
    52  				Type:     schema.TypeString,
    53  				Required: true,
    54  				ForceNew: true,
    55  			},
    56  			"cidr_block": {
    57  				Type:     schema.TypeString,
    58  				Optional: true,
    59  				ForceNew: true,
    60  			},
    61  			"ipv6_cidr_block": {
    62  				Type:     schema.TypeString,
    63  				Optional: true,
    64  				ForceNew: true,
    65  			},
    66  			"from_port": {
    67  				Type:     schema.TypeInt,
    68  				Optional: true,
    69  				ForceNew: true,
    70  			},
    71  			"to_port": {
    72  				Type:     schema.TypeInt,
    73  				Optional: true,
    74  				ForceNew: true,
    75  			},
    76  			"icmp_type": {
    77  				Type:         schema.TypeString,
    78  				Optional:     true,
    79  				ForceNew:     true,
    80  				ValidateFunc: validateICMPArgumentValue,
    81  			},
    82  			"icmp_code": {
    83  				Type:         schema.TypeString,
    84  				Optional:     true,
    85  				ForceNew:     true,
    86  				ValidateFunc: validateICMPArgumentValue,
    87  			},
    88  		},
    89  	}
    90  }
    91  
    92  func resourceAwsNetworkAclRuleCreate(d *schema.ResourceData, meta interface{}) error {
    93  	conn := meta.(*AWSClient).ec2conn
    94  
    95  	protocol := d.Get("protocol").(string)
    96  	p, protocolErr := strconv.Atoi(protocol)
    97  	if protocolErr != nil {
    98  		var ok bool
    99  		p, ok = protocolIntegers()[protocol]
   100  		if !ok {
   101  			return fmt.Errorf("Invalid Protocol %s for rule %d", protocol, d.Get("rule_number").(int))
   102  		}
   103  	}
   104  	log.Printf("[INFO] Transformed Protocol %s into %d", protocol, p)
   105  
   106  	params := &ec2.CreateNetworkAclEntryInput{
   107  		NetworkAclId: aws.String(d.Get("network_acl_id").(string)),
   108  		Egress:       aws.Bool(d.Get("egress").(bool)),
   109  		RuleNumber:   aws.Int64(int64(d.Get("rule_number").(int))),
   110  		Protocol:     aws.String(strconv.Itoa(p)),
   111  		RuleAction:   aws.String(d.Get("rule_action").(string)),
   112  		PortRange: &ec2.PortRange{
   113  			From: aws.Int64(int64(d.Get("from_port").(int))),
   114  			To:   aws.Int64(int64(d.Get("to_port").(int))),
   115  		},
   116  	}
   117  
   118  	cidr, hasCidr := d.GetOk("cidr_block")
   119  	ipv6Cidr, hasIpv6Cidr := d.GetOk("ipv6_cidr_block")
   120  
   121  	if hasCidr == false && hasIpv6Cidr == false {
   122  		return fmt.Errorf("Either `cidr_block` or `ipv6_cidr_block` must be defined")
   123  	}
   124  
   125  	if hasCidr {
   126  		params.CidrBlock = aws.String(cidr.(string))
   127  	}
   128  
   129  	if hasIpv6Cidr {
   130  		params.Ipv6CidrBlock = aws.String(ipv6Cidr.(string))
   131  	}
   132  
   133  	// Specify additional required fields for ICMP. For the list
   134  	// of ICMP codes and types, see: http://www.nthelp.com/icmp.html
   135  	if p == 1 {
   136  		params.IcmpTypeCode = &ec2.IcmpTypeCode{}
   137  		if v, ok := d.GetOk("icmp_type"); ok {
   138  			icmpType, err := strconv.Atoi(v.(string))
   139  			if err != nil {
   140  				return fmt.Errorf("Unable to parse ICMP type %s for rule %d", v, d.Get("rule_number").(int))
   141  			}
   142  			params.IcmpTypeCode.Type = aws.Int64(int64(icmpType))
   143  			log.Printf("[DEBUG] Got ICMP type %d for rule %d", icmpType, d.Get("rule_number").(int))
   144  		}
   145  		if v, ok := d.GetOk("icmp_code"); ok {
   146  			icmpCode, err := strconv.Atoi(v.(string))
   147  			if err != nil {
   148  				return fmt.Errorf("Unable to parse ICMP code %s for rule %d", v, d.Get("rule_number").(int))
   149  			}
   150  			params.IcmpTypeCode.Code = aws.Int64(int64(icmpCode))
   151  			log.Printf("[DEBUG] Got ICMP code %d for rule %d", icmpCode, d.Get("rule_number").(int))
   152  		}
   153  	}
   154  
   155  	log.Printf("[INFO] Creating Network Acl Rule: %d (%t)", d.Get("rule_number").(int), d.Get("egress").(bool))
   156  	_, err := conn.CreateNetworkAclEntry(params)
   157  	if err != nil {
   158  		return fmt.Errorf("Error Creating Network Acl Rule: %s", err.Error())
   159  	}
   160  	d.SetId(networkAclIdRuleNumberEgressHash(d.Get("network_acl_id").(string), d.Get("rule_number").(int), d.Get("egress").(bool), d.Get("protocol").(string)))
   161  
   162  	// It appears it might be a while until the newly created rule is visible via the
   163  	// API (see issue GH-4721). Retry the `findNetworkAclRule` function until it is
   164  	// visible (which in most cases is likely immediately).
   165  	err = resource.Retry(3*time.Minute, func() *resource.RetryError {
   166  		r, findErr := findNetworkAclRule(d, meta)
   167  		if findErr != nil {
   168  			return resource.RetryableError(findErr)
   169  		}
   170  		if r == nil {
   171  			err := fmt.Errorf("Network ACL rule (%s) not found", d.Id())
   172  			return resource.RetryableError(err)
   173  		}
   174  
   175  		return nil
   176  	})
   177  	if err != nil {
   178  		return fmt.Errorf("Created Network ACL Rule was not visible in API within 3 minute period. Running 'terraform apply' again will resume infrastructure creation.")
   179  	}
   180  
   181  	return resourceAwsNetworkAclRuleRead(d, meta)
   182  }
   183  
   184  func resourceAwsNetworkAclRuleRead(d *schema.ResourceData, meta interface{}) error {
   185  	resp, err := findNetworkAclRule(d, meta)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	if resp == nil {
   190  		log.Printf("[DEBUG] Network ACL rule (%s) not found", d.Id())
   191  		d.SetId("")
   192  		return nil
   193  	}
   194  
   195  	d.Set("rule_number", resp.RuleNumber)
   196  	d.Set("cidr_block", resp.CidrBlock)
   197  	d.Set("ipv6_cidr_block", resp.Ipv6CidrBlock)
   198  	d.Set("egress", resp.Egress)
   199  	if resp.IcmpTypeCode != nil {
   200  		d.Set("icmp_code", resp.IcmpTypeCode.Code)
   201  		d.Set("icmp_type", resp.IcmpTypeCode.Type)
   202  	}
   203  	if resp.PortRange != nil {
   204  		d.Set("from_port", resp.PortRange.From)
   205  		d.Set("to_port", resp.PortRange.To)
   206  	}
   207  
   208  	d.Set("rule_action", resp.RuleAction)
   209  
   210  	p, protocolErr := strconv.Atoi(*resp.Protocol)
   211  	log.Printf("[INFO] Converting the protocol %v", p)
   212  	if protocolErr == nil {
   213  		var ok bool
   214  		protocol, ok := protocolStrings(protocolIntegers())[p]
   215  		if !ok {
   216  			return fmt.Errorf("Invalid Protocol %s for rule %d", *resp.Protocol, d.Get("rule_number").(int))
   217  		}
   218  		log.Printf("[INFO] Transformed Protocol %s back into %s", *resp.Protocol, protocol)
   219  		d.Set("protocol", protocol)
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func resourceAwsNetworkAclRuleDelete(d *schema.ResourceData, meta interface{}) error {
   226  	conn := meta.(*AWSClient).ec2conn
   227  
   228  	params := &ec2.DeleteNetworkAclEntryInput{
   229  		NetworkAclId: aws.String(d.Get("network_acl_id").(string)),
   230  		RuleNumber:   aws.Int64(int64(d.Get("rule_number").(int))),
   231  		Egress:       aws.Bool(d.Get("egress").(bool)),
   232  	}
   233  
   234  	log.Printf("[INFO] Deleting Network Acl Rule: %s", d.Id())
   235  	_, err := conn.DeleteNetworkAclEntry(params)
   236  	if err != nil {
   237  		return fmt.Errorf("Error Deleting Network Acl Rule: %s", err.Error())
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  func findNetworkAclRule(d *schema.ResourceData, meta interface{}) (*ec2.NetworkAclEntry, error) {
   244  	conn := meta.(*AWSClient).ec2conn
   245  
   246  	filters := make([]*ec2.Filter, 0, 2)
   247  	ruleNumberFilter := &ec2.Filter{
   248  		Name:   aws.String("entry.rule-number"),
   249  		Values: []*string{aws.String(fmt.Sprintf("%d", d.Get("rule_number").(int)))},
   250  	}
   251  	filters = append(filters, ruleNumberFilter)
   252  	egressFilter := &ec2.Filter{
   253  		Name:   aws.String("entry.egress"),
   254  		Values: []*string{aws.String(fmt.Sprintf("%v", d.Get("egress").(bool)))},
   255  	}
   256  	filters = append(filters, egressFilter)
   257  	params := &ec2.DescribeNetworkAclsInput{
   258  		NetworkAclIds: []*string{aws.String(d.Get("network_acl_id").(string))},
   259  		Filters:       filters,
   260  	}
   261  
   262  	log.Printf("[INFO] Describing Network Acl: %s", d.Get("network_acl_id").(string))
   263  	log.Printf("[INFO] Describing Network Acl with the Filters %#v", params)
   264  	resp, err := conn.DescribeNetworkAcls(params)
   265  	if err != nil {
   266  		return nil, fmt.Errorf("Error Finding Network Acl Rule %d: %s", d.Get("rule_number").(int), err.Error())
   267  	}
   268  
   269  	if resp == nil || len(resp.NetworkAcls) == 0 || resp.NetworkAcls[0] == nil {
   270  		// Missing NACL rule.
   271  		return nil, nil
   272  	}
   273  	if len(resp.NetworkAcls) > 1 {
   274  		return nil, fmt.Errorf(
   275  			"Expected to find one Network ACL, got: %#v",
   276  			resp.NetworkAcls)
   277  	}
   278  	networkAcl := resp.NetworkAcls[0]
   279  	if networkAcl.Entries != nil {
   280  		for _, i := range networkAcl.Entries {
   281  			if *i.RuleNumber == int64(d.Get("rule_number").(int)) && *i.Egress == d.Get("egress").(bool) {
   282  				return i, nil
   283  			}
   284  		}
   285  	}
   286  	return nil, fmt.Errorf(
   287  		"Expected the Network ACL to have Entries, got: %#v",
   288  		networkAcl)
   289  
   290  }
   291  
   292  func networkAclIdRuleNumberEgressHash(networkAclId string, ruleNumber int, egress bool, protocol string) string {
   293  	var buf bytes.Buffer
   294  	buf.WriteString(fmt.Sprintf("%s-", networkAclId))
   295  	buf.WriteString(fmt.Sprintf("%d-", ruleNumber))
   296  	buf.WriteString(fmt.Sprintf("%t-", egress))
   297  	buf.WriteString(fmt.Sprintf("%s-", protocol))
   298  	return fmt.Sprintf("nacl-%d", hashcode.String(buf.String()))
   299  }
   300  
   301  func validateICMPArgumentValue(v interface{}, k string) (ws []string, errors []error) {
   302  	value := v.(string)
   303  	_, err := strconv.Atoi(value)
   304  	if len(value) == 0 || err != nil {
   305  		errors = append(errors, fmt.Errorf("%q must be an integer value: %q", k, value))
   306  	}
   307  	return
   308  }