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 }