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