2015-12-11 12:41:33 -06:00
package aws
import (
"bytes"
"fmt"
"log"
"strconv"
2016-01-19 07:12:20 -06:00
"time"
2015-12-11 12:41:33 -06:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/hashcode"
2016-01-19 07:12:20 -06:00
"github.com/hashicorp/terraform/helper/resource"
2015-12-11 12:41:33 -06:00
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsNetworkAclRule ( ) * schema . Resource {
return & schema . Resource {
Create : resourceAwsNetworkAclRuleCreate ,
Read : resourceAwsNetworkAclRuleRead ,
Delete : resourceAwsNetworkAclRuleDelete ,
Schema : map [ string ] * schema . Schema {
"network_acl_id" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"rule_number" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
ForceNew : true ,
} ,
"egress" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
ForceNew : true ,
Default : false ,
} ,
"protocol" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"rule_action" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"cidr_block" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"from_port" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
ForceNew : true ,
} ,
"to_port" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
ForceNew : true ,
} ,
"icmp_type" : & schema . Schema {
2016-07-16 13:24:48 -05:00
Type : schema . TypeString ,
Optional : true ,
ForceNew : true ,
ValidateFunc : validateICMPArgumentValue ,
2015-12-11 12:41:33 -06:00
} ,
"icmp_code" : & schema . Schema {
2016-07-16 13:24:48 -05:00
Type : schema . TypeString ,
Optional : true ,
ForceNew : true ,
ValidateFunc : validateICMPArgumentValue ,
2015-12-11 12:41:33 -06:00
} ,
} ,
}
}
func resourceAwsNetworkAclRuleCreate ( d * schema . ResourceData , meta interface { } ) error {
conn := meta . ( * AWSClient ) . ec2conn
protocol := d . Get ( "protocol" ) . ( string )
p , protocolErr := strconv . Atoi ( protocol )
if protocolErr != nil {
var ok bool
p , ok = protocolIntegers ( ) [ protocol ]
if ! ok {
2016-07-16 13:24:48 -05:00
return fmt . Errorf ( "Invalid Protocol %s for rule %d" , protocol , d . Get ( "rule_number" ) . ( int ) )
2015-12-11 12:41:33 -06:00
}
}
log . Printf ( "[INFO] Transformed Protocol %s into %d" , protocol , p )
params := & ec2 . CreateNetworkAclEntryInput {
NetworkAclId : aws . String ( d . Get ( "network_acl_id" ) . ( string ) ) ,
Egress : aws . Bool ( d . Get ( "egress" ) . ( bool ) ) ,
RuleNumber : aws . Int64 ( int64 ( d . Get ( "rule_number" ) . ( int ) ) ) ,
Protocol : aws . String ( strconv . Itoa ( p ) ) ,
CidrBlock : aws . String ( d . Get ( "cidr_block" ) . ( string ) ) ,
RuleAction : aws . String ( d . Get ( "rule_action" ) . ( string ) ) ,
PortRange : & ec2 . PortRange {
From : aws . Int64 ( int64 ( d . Get ( "from_port" ) . ( int ) ) ) ,
To : aws . Int64 ( int64 ( d . Get ( "to_port" ) . ( int ) ) ) ,
} ,
}
2016-07-15 13:28:09 -05:00
// Specify additional required fields for ICMP. For the list
// of ICMP codes and types, see: http://www.nthelp.com/icmp.html
2015-12-11 12:41:33 -06:00
if p == 1 {
params . IcmpTypeCode = & ec2 . IcmpTypeCode { }
if v , ok := d . GetOk ( "icmp_type" ) ; ok {
2016-07-15 13:28:09 -05:00
icmpType , err := strconv . Atoi ( v . ( string ) )
if err != nil {
2016-07-16 13:24:48 -05:00
return fmt . Errorf ( "Unable to parse ICMP type %s for rule %d" , v , d . Get ( "rule_number" ) . ( int ) )
2016-07-15 13:28:09 -05:00
}
params . IcmpTypeCode . Type = aws . Int64 ( int64 ( icmpType ) )
2016-07-16 13:24:48 -05:00
log . Printf ( "[DEBUG] Got ICMP type %d for rule %d" , icmpType , d . Get ( "rule_number" ) . ( int ) )
}
if v , ok := d . GetOk ( "icmp_code" ) ; ok {
icmpCode , err := strconv . Atoi ( v . ( string ) )
if err != nil {
return fmt . Errorf ( "Unable to parse ICMP code %s for rule %d" , v , d . Get ( "rule_number" ) . ( int ) )
}
params . IcmpTypeCode . Code = aws . Int64 ( int64 ( icmpCode ) )
log . Printf ( "[DEBUG] Got ICMP code %d for rule %d" , icmpCode , d . Get ( "rule_number" ) . ( int ) )
2015-12-11 12:41:33 -06:00
}
}
2015-12-18 11:21:11 -06:00
log . Printf ( "[INFO] Creating Network Acl Rule: %d (%t)" , d . Get ( "rule_number" ) . ( int ) , d . Get ( "egress" ) . ( bool ) )
2015-12-11 12:41:33 -06:00
_ , err := conn . CreateNetworkAclEntry ( params )
if err != nil {
return fmt . Errorf ( "Error Creating Network Acl Rule: %s" , err . Error ( ) )
}
d . SetId ( networkAclIdRuleNumberEgressHash ( d . Get ( "network_acl_id" ) . ( string ) , d . Get ( "rule_number" ) . ( int ) , d . Get ( "egress" ) . ( bool ) , d . Get ( "protocol" ) . ( string ) ) )
2016-01-19 07:12:20 -06:00
// It appears it might be a while until the newly created rule is visible via the
// API (see issue GH-4721). Retry the `findNetworkAclRule` function until it is
// visible (which in most cases is likely immediately).
2016-03-09 16:53:32 -06:00
err = resource . Retry ( 3 * time . Minute , func ( ) * resource . RetryError {
2016-01-19 07:12:20 -06:00
_ , findErr := findNetworkAclRule ( d , meta )
if findErr != nil {
2016-03-09 16:53:32 -06:00
return resource . RetryableError ( findErr )
2016-01-19 07:12:20 -06:00
}
return nil
} )
if err != nil {
return fmt . Errorf ( "Created Network ACL Rule was not visible in API within 3 minute period. Running 'terraform apply' again will resume infrastructure creation." )
}
2015-12-11 12:41:33 -06:00
return resourceAwsNetworkAclRuleRead ( d , meta )
}
func resourceAwsNetworkAclRuleRead ( d * schema . ResourceData , meta interface { } ) error {
resp , err := findNetworkAclRule ( d , meta )
if err != nil {
return err
}
d . Set ( "rule_number" , resp . RuleNumber )
d . Set ( "cidr_block" , resp . CidrBlock )
d . Set ( "egress" , resp . Egress )
if resp . IcmpTypeCode != nil {
d . Set ( "icmp_code" , resp . IcmpTypeCode . Code )
d . Set ( "icmp_type" , resp . IcmpTypeCode . Type )
}
if resp . PortRange != nil {
d . Set ( "from_port" , resp . PortRange . From )
d . Set ( "to_port" , resp . PortRange . To )
}
d . Set ( "rule_action" , resp . RuleAction )
p , protocolErr := strconv . Atoi ( * resp . Protocol )
log . Printf ( "[INFO] Converting the protocol %v" , p )
if protocolErr == nil {
var ok bool
protocol , ok := protocolStrings ( protocolIntegers ( ) ) [ p ]
if ! ok {
2016-07-16 13:24:48 -05:00
return fmt . Errorf ( "Invalid Protocol %s for rule %d" , * resp . Protocol , d . Get ( "rule_number" ) . ( int ) )
2015-12-11 12:41:33 -06:00
}
log . Printf ( "[INFO] Transformed Protocol %s back into %s" , * resp . Protocol , protocol )
d . Set ( "protocol" , protocol )
}
return nil
}
func resourceAwsNetworkAclRuleDelete ( d * schema . ResourceData , meta interface { } ) error {
conn := meta . ( * AWSClient ) . ec2conn
params := & ec2 . DeleteNetworkAclEntryInput {
NetworkAclId : aws . String ( d . Get ( "network_acl_id" ) . ( string ) ) ,
RuleNumber : aws . Int64 ( int64 ( d . Get ( "rule_number" ) . ( int ) ) ) ,
Egress : aws . Bool ( d . Get ( "egress" ) . ( bool ) ) ,
}
log . Printf ( "[INFO] Deleting Network Acl Rule: %s" , d . Id ( ) )
_ , err := conn . DeleteNetworkAclEntry ( params )
if err != nil {
return fmt . Errorf ( "Error Deleting Network Acl Rule: %s" , err . Error ( ) )
}
return nil
}
func findNetworkAclRule ( d * schema . ResourceData , meta interface { } ) ( * ec2 . NetworkAclEntry , error ) {
conn := meta . ( * AWSClient ) . ec2conn
filters := make ( [ ] * ec2 . Filter , 0 , 2 )
ruleNumberFilter := & ec2 . Filter {
Name : aws . String ( "entry.rule-number" ) ,
2016-07-16 13:24:48 -05:00
Values : [ ] * string { aws . String ( fmt . Sprintf ( "%d" , d . Get ( "rule_number" ) . ( int ) ) ) } ,
2015-12-11 12:41:33 -06:00
}
filters = append ( filters , ruleNumberFilter )
egressFilter := & ec2 . Filter {
Name : aws . String ( "entry.egress" ) ,
Values : [ ] * string { aws . String ( fmt . Sprintf ( "%v" , d . Get ( "egress" ) . ( bool ) ) ) } ,
}
filters = append ( filters , egressFilter )
params := & ec2 . DescribeNetworkAclsInput {
NetworkAclIds : [ ] * string { aws . String ( d . Get ( "network_acl_id" ) . ( string ) ) } ,
Filters : filters ,
}
log . Printf ( "[INFO] Describing Network Acl: %s" , d . Get ( "network_acl_id" ) . ( string ) )
log . Printf ( "[INFO] Describing Network Acl with the Filters %#v" , params )
resp , err := conn . DescribeNetworkAcls ( params )
if err != nil {
return nil , fmt . Errorf ( "Error Finding Network Acl Rule %d: %s" , d . Get ( "rule_number" ) . ( int ) , err . Error ( ) )
}
if resp == nil || len ( resp . NetworkAcls ) != 1 || resp . NetworkAcls [ 0 ] == nil {
return nil , fmt . Errorf (
"Expected to find one Network ACL, got: %#v" ,
resp . NetworkAcls )
}
networkAcl := resp . NetworkAcls [ 0 ]
if networkAcl . Entries != nil {
for _ , i := range networkAcl . Entries {
if * i . RuleNumber == int64 ( d . Get ( "rule_number" ) . ( int ) ) && * i . Egress == d . Get ( "egress" ) . ( bool ) {
return i , nil
}
}
}
return nil , fmt . Errorf (
"Expected the Network ACL to have Entries, got: %#v" ,
networkAcl )
}
func networkAclIdRuleNumberEgressHash ( networkAclId string , ruleNumber int , egress bool , protocol string ) string {
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( "%s-" , networkAclId ) )
buf . WriteString ( fmt . Sprintf ( "%d-" , ruleNumber ) )
buf . WriteString ( fmt . Sprintf ( "%t-" , egress ) )
buf . WriteString ( fmt . Sprintf ( "%s-" , protocol ) )
return fmt . Sprintf ( "nacl-%d" , hashcode . String ( buf . String ( ) ) )
}
2016-07-16 13:24:48 -05:00
func validateICMPArgumentValue ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
value := v . ( string )
_ , err := strconv . Atoi ( value )
if len ( value ) == 0 || err != nil {
errors = append ( errors , fmt . Errorf ( "%q must be an integer value: %q" , k , value ) )
}
return
}