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